using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; 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.Interfaces; using LethalAPI.TerminalCommands.Models; using LethalAPI.TerminalCommands.Models.Enums; 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.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: AssemblyTitle("TerminalCommandsFixed")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("TerminalCommandsFixed")] [assembly: AssemblyCopyright("Copyright © 2026")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("dba144e8-d93c-4405-b395-e09fff5de671")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [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] [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 LethalAPI.TerminalCommands { [BepInPlugin("LethalAPI.TerminalCommands", "LethalAPI.TerminalCommands", "1.1.0")] public class TerminalCommandsPlugin : BaseUnityPlugin { private readonly Harmony m_Harmony = new Harmony("LethalAPI.TerminalCommands"); private TerminalModRegistry? m_Registry; private void Awake() { ((BaseUnityPlugin)this).Logger.LogInfo((object)"LethalAPI.TerminalCommands is loading..."); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Installing patches"); m_Harmony.PatchAll(typeof(TerminalCommandsPlugin).Assembly); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Registering built-in Commands"); m_Registry = TerminalRegistry.CreateTerminalRegistry(); m_Registry.RegisterFrom(); Object.DontDestroyOnLoad((Object)(object)this); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Plugin LethalAPI.TerminalCommands is loaded!"); } } 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"; } 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 TerminalNode? GetLastLoadedNode() { return LoadNewNodePatch.LastLoadedNode; } public static TerminalNode? GetLastLoadedNode(this Terminal terminal) { return LoadNewNodePatch.LastLoadedNode ?? terminal.currentNode; } } public static class TerminalNodeExtensions { public static TerminalNode WithDisplayText(this TerminalNode node, object? displayText) { node.displayText = displayText?.ToString() ?? string.Empty; return node; } public static TerminalNode WithDisplayTexture(this TerminalNode node, Texture texture) { node.displayTexture = texture; return node; } public static TerminalNode WithVideoClip(this TerminalNode node, VideoClip video) { node.displayVideo = video; return node; } public static TerminalNode WithAcceptsAnything(this TerminalNode node) { node.acceptAnything = true; return node; } public static TerminalNode WithBuyItemIndex(this TerminalNode node, int index) { node.buyItemIndex = index; return node; } public static TerminalNode WithMoonIndex(this TerminalNode node, int index) { node.buyRerouteToMoon = index; return node; } public static TerminalNode ClearText(this TerminalNode node) { node.clearPreviousText = true; return node; } public static TerminalNode WithItemCost(this TerminalNode node, int cost) { node.itemCost = cost; return node; } public static TerminalNode LoadImageSlowly(this TerminalNode node) { node.loadImageSlowly = true; return node; } public static TerminalNode PersistImage(this TerminalNode node) { node.persistentImage = true; return node; } public static TerminalNode WithAudio(this TerminalNode node, AudioClip audio) { node.playClip = audio; return node; } public static TerminalNode WithTerminalEvent(this TerminalNode node, string eventName) { node.terminalEvent = eventName; return node; } public static TerminalNode ReturnItemFromStorage(this TerminalNode node, int unlockID) { node.shipUnlockableID = unlockID; node.returnFromStorage = true; return node; } public static TerminalNode PlayEffect(this TerminalNode node, SyncedTerminalClip clip) { node.playSyncedClip = (int)clip; return node; } public static TerminalNode PlayError(this TerminalNode node) { node.playSyncedClip = 1; return node; } } public class TerminalRegistry { private static readonly ConcurrentDictionary> m_RegisteredCommands = new ConcurrentDictionary>(StringComparer.InvariantCultureIgnoreCase); public static TerminalModRegistry RegisterFrom(T instance) where T : class { TerminalModRegistry terminalModRegistry = new TerminalModRegistry(); terminalModRegistry.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(); 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 GetCommands(string commandName) { if (m_RegisteredCommands.TryGetValue(commandName, out var value)) { return value; } return new List(); } public static IEnumerable EnumerateCommands(string name) { if (!m_RegisteredCommands.TryGetValue(name, out var value)) { return Enumerable.Empty(); } return value; } public static IEnumerable EnumerateCommands() { string[] keys = m_RegisteredCommands.Keys.ToArray(); for (int i = 0; i < keys.Length; i++) { List overloads = m_RegisteredCommands[keys[i]]; for (int c = 0; c < overloads.Count; c++) { yield return overloads[c]; } } } public static IEnumerable GetCommandMethods() { MethodInfo[] methods = typeof(T).GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo method in methods) { if (method.GetCustomAttribute() != null) { yield return method; } } } } } namespace LethalAPI.TerminalCommands.Patches { [HarmonyPatch(typeof(Terminal), "BeginUsingTerminal")] internal static class BeginUsingTerminalPatch { [HarmonyPostfix] public static void Postfix(Terminal __instance) { ITerminalInterface currentInterface = CommandHandler.CurrentInterface; if (currentInterface != null) { TerminalNode splashScreen = currentInterface.GetSplashScreen(__instance); if ((Object)(object)splashScreen != (Object)null) { __instance.LoadNewNode(splashScreen); } } } } [HarmonyPatch(typeof(Terminal), "LoadNewNode")] internal static class LoadNewNodePatch { public static TerminalNode? LastLoadedNode { get; private set; } [HarmonyPrefix] public static void Prefix(TerminalNode node) { LastLoadedNode = node; } } [HarmonyPatch(typeof(Terminal), "ParsePlayerSentence")] internal 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.HandleCommandInput(command, __instance); return (Object)(object)__state == (Object)null; } [HarmonyPostfix] public static TerminalNode? ParsePostfix(TerminalNode? __result, TerminalNode? __state, Terminal __instance) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0024: 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; } } [HarmonyPatch(typeof(Terminal), "selectTextFieldDelayed")] internal 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")] internal static class TerminalSubmitPatch { private static readonly ManualLogSource m_LogSource = new ManualLogSource("LethalAPI.Terminal"); public static TerminalNode? LastNode { get; set; } [HarmonyPrefix] public static void Prefix() { LastNode = null; } [HarmonyPostfix] public static void Postfix(Terminal __instance, ref Coroutine ___forceScrollbarCoroutine) { if (!((Object)(object)LastNode == (Object)null) && !LastNode.clearPreviousText) { if (___forceScrollbarCoroutine != null) { ((MonoBehaviour)__instance).StopCoroutine(___forceScrollbarCoroutine); ___forceScrollbarCoroutine = null; } ___forceScrollbarCoroutine = ((MonoBehaviour)__instance).StartCoroutine("forceScrollbarDown"); } } } [HarmonyPatch(typeof(Terminal), "TextPostProcess")] internal static class TextPostProcessPatch { [HarmonyPrefix] public static bool Prefix(Terminal __instance, ref string modifiedDisplayText, ref string? __state) { __state = null; if (CommandHandler.CurrentInterface == null || CommandHandler.CurrentInterface.APITextPostProcessing) { Console.WriteLine("Run text post process"); modifiedDisplayText = TextUtil.PostProcessResponse(__instance, modifiedDisplayText); return true; } bool flag = ProcessInterface(CommandHandler.CurrentInterface, ref modifiedDisplayText, __instance); if (!flag) { __state = modifiedDisplayText; } return flag; } [HarmonyPostfix] public static string? Postfix(string __result, Terminal __instance, string? __state) { string text = __state ?? __result; if (CommandHandler.CurrentInterface != null) { text = CommandHandler.CurrentInterface.PostProcessText(__instance, text); } return text; } private static bool ProcessInterface(ITerminalInterface terminalInterface, ref string modifiedDisplayText, Terminal terminal) { modifiedDisplayText = terminalInterface.PreProcessText(terminal, modifiedDisplayText); return terminalInterface.VanillaTextPostProcessing; } } } namespace LethalAPI.TerminalCommands.Models { public class ArgumentStream { public string[] Arguments { get; } public int Index { get; set; } = 0; public bool EndOfStream => Index >= Arguments.Length; public ArgumentStream(string[] arguments) { Arguments = arguments; } public ArgumentStream(IEnumerable arguments) { Arguments = arguments.ToArray(); } 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 static class CommandActivator { public static bool TryCreateInvoker(ArgumentStream arguments, ServiceCollection services, MethodInfo method, out Func? invoker) { TerminalCommandAttribute commandAttribute = method.GetCustomAttribute(); ParameterInfo[] parameters = method.GetParameters(); object?[] values = new object[parameters.Length]; invoker = null; for (int i = 0; i < parameters.Length; i++) { ParameterInfo parameterInfo = parameters[i]; Type parameterType = parameterInfo.ParameterType; if (services.TryGetService(parameterType, out object service)) { values[i] = service; } else if (parameterType == typeof(string) && parameterInfo.GetCustomAttribute() != null) { if (!arguments.TryReadRemaining(out string result) || result == null) { return false; } values[i] = result; } else { if (!arguments.TryReadNext(parameterType, out object value) || value == null) { return false; } values[i] = value; } } arguments.Reset(); invoker = (object instance) => ExecuteCommand(method, instance, values, commandAttribute?.ClearText ?? false); return true; } private static object? ExecuteCommand(MethodInfo method, object instance, object?[] arguments, bool clearConsole) { object obj; try { obj = method.Invoke(instance, arguments); } catch (Exception ex) { Console.WriteLine("Error caught while invoking command hander: " + ex.Message); Console.WriteLine(ex.StackTrace); return null; } if (obj == null) { return null; } Type type = obj.GetType(); if (typeof(TerminalNode).IsAssignableFrom(type)) { return obj; } if (typeof(ITerminalInteraction).IsAssignableFrom(type)) { return obj; } if (typeof(ITerminalInterface).IsAssignableFrom(type)) { return obj; } TerminalNode val = ScriptableObject.CreateInstance(); val.displayText = obj.ToString() + "\n"; val.clearPreviousText = clearConsole; return val; } } public class CommandComparer : IComparer { 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 Stack m_Interactions = new Stack(); private static readonly Regex m_SplitRegex = new Regex("[\\\"](.+?)[\\\"]|([^ ]+)", RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture | RegexOptions.Compiled); private static readonly CommandComparer m_Comparer = new CommandComparer(); public static int InteractionDepth => m_Interactions.Count; public static ITerminalInterface? CurrentInterface { get; private set; } public static TerminalNode? HandleCommandInput(string command, Terminal terminal) { MatchCollection source = m_SplitRegex.Matches(command.Trim()); string[] array = (from Match x in source select x.Value.Trim('"', ' ')).ToArray(); ArgumentStream arguments = new ArgumentStream(array); if (CurrentInterface != null) { return CurrentInterface.HandleInput(terminal, arguments); } TerminalNode val = ExecuteInteractions(arguments, terminal); if ((Object)(object)val != (Object)null) { return val; } string commandName = array.First(); ArgumentStream arguments2 = new ArgumentStream(array.Skip(1)); return ExecuteCommand(commandName, arguments2, terminal); } public static TerminalNode? ExecuteInteractions(ArgumentStream arguments, Terminal terminal) { while (m_Interactions.Count > 0) { ITerminalInteraction terminalInteraction = m_Interactions.Pop(); arguments.Reset(); try { terminalInteraction.Services.WithServices(arguments, terminal, arguments.Arguments); object obj = terminalInteraction.HandleTerminalResponse(arguments); if (obj != null) { return HandleCommandResult(obj, terminal); } } catch (Exception ex) { Console.WriteLine(ex.GetType().Name); Console.WriteLine("Error executing interaction: " + ex.Message + ", " + ex.StackTrace); } } return null; } public static TerminalNode? ExecuteCommand(ArgumentStream arguments, Terminal terminal) { arguments.Reset(); if (!arguments.TryReadNext(out string value)) { return null; } ArgumentStream arguments2 = new ArgumentStream(arguments.Arguments.Skip(1)); return ExecuteCommand(value, arguments2, terminal); } public static TerminalNode? ExecuteCommand(string commandName, ArgumentStream arguments, Terminal terminal) { List<(TerminalCommand, Func)> list = new List<(TerminalCommand, Func)>(); TerminalCommand[] array = TerminalRegistry.GetCommands(commandName).ToArray(); ServiceCollection services = new ServiceCollection(arguments, arguments.Arguments, terminal); foreach (TerminalCommand terminalCommand in array) { if (!terminalCommand.CheckAllowed()) { continue; } arguments.Reset(); if (terminalCommand.TryCreateInvoker(arguments, services, out Func invoker) && invoker != null) { Func item = () => HandleCommandResult(invoker(), terminal); list.Add((terminalCommand, item)); } } IOrderedEnumerable<(TerminalCommand, Func)> orderedEnumerable = list.OrderByDescending<(TerminalCommand, Func), TerminalCommand>(((TerminalCommand command, Func invoker) x) => x.command, m_Comparer); foreach (var item4 in orderedEnumerable) { TerminalCommand item2 = item4.Item1; Func item3 = item4.Item2; TerminalNode val = item3(); if ((Object)(object)val != (Object)null) { return val; } } return null; } private static TerminalNode? HandleCommandResult(object? result, Terminal terminal) { TerminalNode val = (TerminalNode)((result is TerminalNode) ? result : null); if (val != null) { return val; } if (result is ITerminalInteraction terminalInteraction) { SetInteraction(terminalInteraction); return terminalInteraction.Prompt; } if (result is ITerminalInterface terminalInterface) { SetInterface(terminalInterface); return terminalInterface.GetSplashScreen(terminal); } return ScriptableObject.CreateInstance().WithDisplayText(result); } public static void SetInteraction(ITerminalInteraction interaction) { m_Interactions.Push(interaction); } public static void SetInterface(ITerminalInterface terminalInterface) { CurrentInterface = terminalInterface; } public static void ResetInterface() { CurrentInterface = null; } public static void ResetInterface() where T : ITerminalInterface { if (CurrentInterface != null && CurrentInterface is T) { ResetInterface(); } } } 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(); } [StringConverter] 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)StartOfRound.Instance.allPlayerScripts).FirstOrDefault((Func)((PlayerControllerB x) => x.playerSteamId == steamID)); } if ((Object)(object)val == (Object)null) { val = ((IEnumerable)StartOfRound.Instance.allPlayerScripts).FirstOrDefault((Func)((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 readonly struct RegisteredStringConverter { public Type Type { get; } public StringConversionHandler Handler { get; } public RegisteredStringConverter(Type type, StringConversionHandler handler) { Type = type; Handler = handler; } } public struct ServiceCollection { private Dictionary m_Services; public ServiceCollection(params object[] services) { m_Services = new Dictionary(); WithServices(services); } public ServiceCollection() { m_Services = new Dictionary(); } public bool TryGetService(Type t, out object? service) { if (m_Services == null) { service = null; return false; } return m_Services.TryGetValue(t, out service); } public void WithService(T instance) { if (m_Services != null) { m_Services[typeof(T)] = instance; } } public void WithServices(params object[] services) { if (m_Services == null) { return; } foreach (object obj in services) { if (obj != null) { m_Services.Add(obj.GetType(), obj); } } } } public delegate object StringConversionHandler(string value); public static class StringConverter { private static bool m_Initialized = false; private static ConcurrentDictionary> m_StringConverters { get; } = new ConcurrentDictionary>(); public static bool TryConvert(string value, Type type, out object? result) { if (!m_Initialized) { m_Initialized = true; RegisterFromType(typeof(DefaultStringConverters)); } if (!m_StringConverters.TryGetValue(type, out var value2)) { result = null; return false; } for (int i = 0; i < value2.Count; i++) { try { result = value2[i](value); return true; } catch (Exception) { } } result = null; return false; } public static List RegisterFrom(T instance) where T : class { return RegisterFromType(typeof(T), instance); } public static bool Deregister(RegisteredStringConverter converter) { if (!m_StringConverters.TryGetValue(converter.Type, out var value)) { return false; } return value.Remove(converter.Handler); } public static bool CanConvert(Type type) { return m_StringConverters.ContainsKey(type); } public static List RegisterFromType(Type type, object? instance = null) { List list = new List(); MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo method in methods) { if (method.GetCustomAttribute() == null) { continue; } ParameterInfo[] parameters = method.GetParameters(); if (parameters.Length == 1 && !(parameters[0].ParameterType != typeof(string))) { Type returnType = method.ReturnType; StringConversionHandler stringConversionHandler = (string text) => method.Invoke(instance, new object[1] { text }); if (!m_StringConverters.TryGetValue(returnType, out var value)) { value = new List(); m_StringConverters[returnType] = value; } value.Add(stringConversionHandler); list.Add(new RegisteredStringConverter(returnType, stringConversionHandler)); } } return list; } } public class TerminalCommand { private ManualLogSource m_LogSource = new ManualLogSource("LethalAPI.Terminal"); 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() { IEnumerable customAttributes = Method.GetCustomAttributes(); foreach (AccessControlAttribute item in customAttributes) { if (!item.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(); if (customAttribute != null) { text = text ?? customAttribute.CommandName; clearConsole = customAttribute.ClearText; } CommandInfoAttribute customAttribute2 = info.GetCustomAttribute(); if (customAttribute2 != null) { syntax = customAttribute2.Syntax; description = customAttribute2.Description; } CommandPriority customAttribute3 = info.GetCustomAttribute(); if (customAttribute3 != null) { priority = customAttribute3.Priority; } if (text == null) { throw new ArgumentException("No override name provided, and command name could not be resolved from method"); } return new TerminalCommand(text, info, instance, clearConsole, syntax, description, priority); } public bool TryCreateInvoker(ArgumentStream arguments, ServiceCollection services, out Func? invoker) { if (CommandActivator.TryCreateInvoker(arguments, services, Method, out Func activatedInvoker) && activatedInvoker != null) { invoker = () => activatedInvoker(Instance); return true; } invoker = null; return false; } } public class TerminalModRegistry { public List Commands { get; } = new List(); public List StringConverters { get; } = new List(); public T RegisterFrom() where T : class, new() { return RegisterFrom(new T()); } public T RegisterFrom(T instance) where T : class { foreach (MethodInfo commandMethod in TerminalRegistry.GetCommandMethods()) { TerminalCommand terminalCommand = TerminalCommand.FromMethod(commandMethod, instance); TerminalRegistry.RegisterCommand(terminalCommand); lock (Commands) { Commands.Add(terminalCommand); } } StringConverters.AddRange(StringConverter.RegisterFrom(instance)); return instance; } public void Deregister() { for (int i = 0; i < Commands.Count; i++) { TerminalRegistry.Deregister(Commands[i]); } Commands.Clear(); for (int j = 0; j < StringConverters.Count; j++) { StringConverter.Deregister(StringConverters[j]); } StringConverters.Clear(); } } public static class TextUtil { public static string PostProcessResponse(Terminal terminal, string message) { TerminalNode? lastLoadedNode = terminal.GetLastLoadedNode(); message = ((lastLoadedNode != null && !lastLoadedNode.clearPreviousText) ? message.TrimStart('\n', ' ') : message.SetStartPadding('\n', 2)); message = message.SetEndPadding('\n', 2); return message; } public static string SetEndPadding(this string text, char character, int minPadding) { string text2 = text; int num = 0; int num2 = text.Length - 1; while (num2 >= 0 && text[num2] == character) { num++; num2--; } int num3 = minPadding - num; if (num3 > 0) { text2 += new string(character, num3); } return text2; } public static string SetStartPadding(this string text, char character, int minPadding) { string text2 = text; int num = 0; for (int i = 0; i < text.Length && text[i] == character; i++) { num++; } int num2 = minPadding - num; if (num2 > 0) { text2 = new string(character, num2) + text2; } return text2; } } } namespace LethalAPI.TerminalCommands.Models.Enums { public enum AllowedCaller { None = -1, Player, Host } public enum SyncedTerminalClip { ItemPurchased, Error, BroadcastEffect } } namespace LethalAPI.TerminalCommands.Interfaces { public interface ITerminalInteraction { TerminalNode? Prompt { get; } ServiceCollection Services { get; } object? HandleTerminalResponse(ArgumentStream arguments); } public interface ITerminalInterface { bool APITextPostProcessing { get; } bool VanillaTextPostProcessing { get; } TerminalNode HandleInput(Terminal instance, ArgumentStream arguments); string PreProcessText(Terminal instance, string text); string PostProcessText(Terminal terminal, string text); TerminalNode GetSplashScreen(Terminal terminal); } } namespace LethalAPI.TerminalCommands.Interactions { public class ConfirmInteraction : ITerminalInteraction { public TerminalNode? Prompt { get; private set; } public ServiceCollection Services { get; } = new ServiceCollection(); public Delegate? ConfirmHandler { get; set; } public Delegate? DenyHandler { get; set; } public ConfirmInteraction() { } public ConfirmInteraction(TerminalNode prompt) { Prompt = prompt; PostprocessPrompt(); } public ConfirmInteraction(Action promptBuilder) { TerminalNode val = ScriptableObject.CreateInstance(); promptBuilder(val); WithPrompt(val); } public ConfirmInteraction(TerminalNode prompt, Delegate confirm, Delegate deny) { WithPrompt(prompt); ConfirmHandler = confirm; DenyHandler = deny; } public ConfirmInteraction(Action promptBuilder, Delegate confirm, Delegate deny) { WithPrompt(promptBuilder); ConfirmHandler = confirm; DenyHandler = deny; } public ConfirmInteraction(string prompt) { WithPrompt(prompt); } public ConfirmInteraction(string prompt, Delegate confirm, Delegate deny) { WithPrompt(prompt); ConfirmHandler = confirm; DenyHandler = deny; } public ConfirmInteraction WithPrompt(string prompt) { Prompt = ScriptableObject.CreateInstance(); Prompt.WithDisplayText(prompt); PostprocessPrompt(); return this; } public ConfirmInteraction WithPrompt(TerminalNode prompt) { Prompt = prompt; PostprocessPrompt(); return this; } public ConfirmInteraction WithPrompt(Action promptBuilder) { TerminalNode val = ScriptableObject.CreateInstance(); promptBuilder(val); WithPrompt(val); return this; } public ConfirmInteraction Confirm(Delegate confirmHandler) { ConfirmHandler = confirmHandler; return this; } public ConfirmInteraction Deny(Delegate denyHandler) { DenyHandler = denyHandler; return this; } public ConfirmInteraction WithContext(params object[] services) { Services.WithServices(services); return this; } public object? HandleTerminalResponse(ArgumentStream arguments) { if (!arguments.TryReadNext(out string value)) { return Deny(arguments); } if (value.Trim().Equals("confirm", StringComparison.InvariantCultureIgnoreCase)) { return Confirm(arguments); } return Deny(arguments); } private object? Confirm(ArgumentStream arguments) { if ((object)DenyHandler == null) { return string.Empty; } if ((object)ConfirmHandler != null && CommandActivator.TryCreateInvoker(arguments, Services, ConfirmHandler.GetMethodInfo(), out Func invoker) && invoker != null) { return invoker(ConfirmHandler.Target); } return null; } private object? Deny(ArgumentStream arguments) { if ((object)DenyHandler == null) { return string.Empty; } if (CommandActivator.TryCreateInvoker(arguments, Services, DenyHandler.GetMethodInfo(), out Func invoker) && invoker != null) { return invoker(DenyHandler.Target); } return null; } private void PostprocessPrompt() { if ((Object)(object)Prompt != (Object)null) { string text = Prompt.displayText ?? string.Empty; text = text.SetEndPadding('\n', 2) + "Please CONFIRM or DENY.\n"; Prompt.displayText = text; } } } public class TerminalInteraction : ITerminalInteraction { public TerminalNode? Prompt { get; private set; } public ServiceCollection Services { get; } = new ServiceCollection(); public List Handlers { get; } = new List(); public TerminalInteraction() { } public TerminalInteraction(TerminalNode prompt, Delegate handler) { Prompt = prompt; Handlers.Add(handler); } public TerminalInteraction(Action promptBuilder, Delegate handler) { TerminalNode val = ScriptableObject.CreateInstance(); promptBuilder(val); WithPrompt(val); Handlers.Add(handler); } public TerminalInteraction WithContext(params object[] services) { Services.WithServices(services); return this; } public TerminalInteraction WithPrompt(TerminalNode prompt) { Prompt = prompt; return this; } public TerminalInteraction WithPrompt(Action promptBuilder) { TerminalNode val = ScriptableObject.CreateInstance(); promptBuilder(val); WithPrompt(val); return this; } public TerminalInteraction WithPrompt(string prompt) { Prompt = ScriptableObject.CreateInstance(); Prompt.WithDisplayText(prompt); return this; } public TerminalInteraction WithHandler(Delegate handler) { Handlers.Add(handler); return this; } public object? HandleTerminalResponse(ArgumentStream arguments) { List<(MethodInfo, object)> list = (from x in Handlers select (info: x.GetMethodInfo(), instance: x.Target) into x orderby x.info.GetParameters().Length descending select x).ToList(); foreach (Delegate handler in Handlers) { MethodInfo methodInfo = handler.GetMethodInfo(); arguments.Reset(); if (CommandActivator.TryCreateInvoker(arguments, Services, methodInfo, out Func invoker) && invoker != null) { object obj = invoker(handler.Target); if (obj != null) { return obj; } } } return null; } } } 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(); stringBuilder.AppendLine(">TRANSMIT [message]"); stringBuilder.AppendLine("Transmit a message with the signal translator"); 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.Parameter)] public sealed class RemainingTextAttribute : Attribute { } public class RequireInterfaceAttribute : AccessControlAttribute { public Type InterfaceType { get; } public RequireInterfaceAttribute(Type interfaceType) { InterfaceType = interfaceType; } public override bool CheckAllowed() { return CommandHandler.CurrentInterface != null && CommandHandler.CurrentInterface.GetType() == InterfaceType; } } public sealed class RequireInterfaceAttribute : RequireInterfaceAttribute where T : ITerminalInterface { public RequireInterfaceAttribute() : base(typeof(T)) { } } 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; } } }