Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of jivanf LethalAPI Terminal v1.0.0
BepInEx/plugins/LethalAPI.Terminal.dll
Decompiled 8 months agousing 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.LibTerminal.Attributes; using LethalAPI.LibTerminal.Commands; using LethalAPI.LibTerminal.Interfaces; using LethalAPI.LibTerminal.Models; using LethalAPI.LibTerminal.Models.Enums; using LethalAPI.LibTerminal.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(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [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.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+87604738e7f6701f3f530dadb2d90e906f8dba06")] [assembly: AssemblyProduct("LethalAPI.Terminal")] [assembly: AssemblyTitle("LethalAPI.Terminal")] [assembly: AssemblyMetadata("RepositoryUrl", "https://github.com/jivanf/LethalAPI.Terminal")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.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.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] [Microsoft.CodeAnalysis.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] [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.LibTerminal { [BepInPlugin("me.jivanf.lethalapi-terminal", "LethalAPI.Terminal", "1.0.0")] internal class LibTerminalPlugin : BaseUnityPlugin { private readonly Harmony m_Harmony = new Harmony("me.jivanf.lethalapi-terminal"); private TerminalModRegistry? m_Registry; private void Awake() { ((BaseUnityPlugin)this).Logger.LogInfo((object)"me.jivanf.lethalapi-terminal is loading..."); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Installing patches"); m_Harmony.PatchAll(typeof(LibTerminalPlugin).Assembly); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Registering built-in Commands"); m_Registry = TerminalRegistry.CreateTerminalRegistry(); m_Registry.RegisterFrom<CommandInfoCommands>(); Object.DontDestroyOnLoad((Object)(object)this); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Plugin me.jivanf.lethalapi-terminal 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 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<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(); 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 List<TerminalCommand> 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 List<TerminalCommand> value)) { return; } lock (value) { value.Remove(command); } } public static IReadOnlyList<TerminalCommand> GetCommands(string commandName) { if (m_RegisteredCommands.TryGetValue(commandName, out List<TerminalCommand> value)) { return value; } return new List<TerminalCommand>(); } public static IEnumerable<TerminalCommand> EnumerateCommands(string name) { if (!m_RegisteredCommands.TryGetValue(name, out List<TerminalCommand> 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; } } } } public static class PluginInfo { public const string PLUGIN_GUID = "me.jivanf.lethalapi-terminal"; public const string PLUGIN_NAME = "LethalAPI.Terminal"; public const string PLUGIN_VERSION = "1.0.0"; } } namespace LethalAPI.LibTerminal.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_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; } } [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 ManualLogSource m_LogSource = new ManualLogSource("LethalAPI.Terminal"); 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")] 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 num = ProcessInterface(CommandHandler.CurrentInterface, ref modifiedDisplayText, __instance); if (!num) { __state = modifiedDisplayText; } return num; } [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.LibTerminal.Models { 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 ArgumentStream(IEnumerable<string> 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<object, object?>? invoker) { MethodInfo method2 = method; TerminalCommandAttribute commandAttribute = method2.GetCustomAttribute<TerminalCommandAttribute>(); ParameterInfo[] parameters = method2.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<RemainingTextAttribute>() != 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(method2, 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 obj2 = ScriptableObject.CreateInstance<TerminalNode>(); obj2.displayText = obj.ToString() + "\n"; obj2.clearPreviousText = clearConsole; return obj2; } } 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 Stack<ITerminalInteraction> m_Interactions = new Stack<ITerminalInteraction>(); 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) { string[] array = (from Match x in m_SplitRegex.Matches(command.Trim()) 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) { Terminal terminal2 = terminal; List<(TerminalCommand, Func<TerminalNode>)> list = new List<(TerminalCommand, Func<TerminalNode>)>(); TerminalCommand[] array = TerminalRegistry.GetCommands(commandName).ToArray(); ServiceCollection services = new ServiceCollection(arguments, arguments.Arguments, terminal2); foreach (TerminalCommand terminalCommand in array) { if (!terminalCommand.CheckAllowed()) { continue; } arguments.Reset(); if (terminalCommand.TryCreateInvoker(arguments, services, out Func<object?> invoker) && invoker != null) { Func<TerminalNode> item = () => HandleCommandResult(invoker(), terminal2); list.Add((terminalCommand, item)); } } foreach (var item2 in list.OrderByDescending<(TerminalCommand, Func<TerminalNode>), TerminalCommand>(((TerminalCommand command, Func<TerminalNode> invoker) x) => x.command, m_Comparer)) { TerminalNode val = item2.Item2(); if ((Object)(object)val != (Object)null) { return val; } } return null; } private static TerminalNode? HandleCommandResult(object? result, Terminal terminal) { if (result == null) { return null; } 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<TerminalNode>().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<T>() 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) { string value2 = value; if ((Object)(object)StartOfRound.Instance == (Object)null) { throw new ArgumentException("Game has not started"); } PlayerControllerB val = null; if (ulong.TryParse(value2, 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(value2, 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<Type, object?> m_Services; public ServiceCollection(params object[] services) { m_Services = new Dictionary<Type, object>(); WithServices(services); } public ServiceCollection() { m_Services = new Dictionary<Type, object>(); } 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>(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<Type, List<StringConversionHandler>> m_StringConverters { get; } = new ConcurrentDictionary<Type, List<StringConversionHandler>>(); 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 List<StringConversionHandler> 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<RegisteredStringConverter> RegisterFrom<T>(T instance) where T : class { return RegisterFromType(typeof(T), instance); } public static bool Deregister(RegisteredStringConverter converter) { if (!m_StringConverters.TryGetValue(converter.Type, out List<StringConversionHandler> value)) { return false; } return value.Remove(converter.Handler); } public static bool CanConvert(Type type) { return m_StringConverters.ContainsKey(type); } public static List<RegisteredStringConverter> RegisterFromType(Type type, object? instance = null) { object instance2 = instance; List<RegisteredStringConverter> list = new List<RegisteredStringConverter>(); 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 stringConversionHandler = (string value) => method.Invoke(instance2, new object[1] { value }); if (!m_StringConverters.TryGetValue(returnType, out List<StringConversionHandler> value2)) { value2 = new List<StringConversionHandler>(); m_StringConverters[returnType] = value2; } value2.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() { 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; } 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<object?>? invoker) { if (CommandActivator.TryCreateInvoker(arguments, services, Method, out Func<object, object?> activatedInvoker) && activatedInvoker != null) { invoker = () => activatedInvoker(Instance); return true; } invoker = null; return false; } } public class TerminalModRegistry { public List<TerminalCommand> Commands { get; } = new List<TerminalCommand>(); public List<RegisteredStringConverter> StringConverters { get; } = new List<RegisteredStringConverter>(); 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); } } 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.LibTerminal.Models.Enums { public enum AllowedCaller { None = -1, Player, Host } public enum SyncedTerminalClip { ItemPurchased, Error, BroadcastEffect } } namespace LethalAPI.LibTerminal.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.LibTerminal.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<TerminalNode> promptBuilder) { TerminalNode val = ScriptableObject.CreateInstance<TerminalNode>(); promptBuilder(val); WithPrompt(val); } public ConfirmInteraction(TerminalNode prompt, Delegate confirm, Delegate deny) { WithPrompt(prompt); ConfirmHandler = confirm; DenyHandler = deny; } public ConfirmInteraction(Action<TerminalNode> 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<TerminalNode>(); Prompt.WithDisplayText(prompt); PostprocessPrompt(); return this; } public ConfirmInteraction WithPrompt(TerminalNode prompt) { Prompt = prompt; PostprocessPrompt(); return this; } public ConfirmInteraction WithPrompt(Action<TerminalNode> promptBuilder) { TerminalNode val = ScriptableObject.CreateInstance<TerminalNode>(); 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<object, object> 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<object, object> 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<Delegate> Handlers { get; } = new List<Delegate>(); public TerminalInteraction() { } public TerminalInteraction(TerminalNode prompt, Delegate handler) { Prompt = prompt; Handlers.Add(handler); } public TerminalInteraction(Action<TerminalNode> promptBuilder, Delegate handler) { TerminalNode val = ScriptableObject.CreateInstance<TerminalNode>(); 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<TerminalNode> promptBuilder) { TerminalNode val = ScriptableObject.CreateInstance<TerminalNode>(); promptBuilder(val); WithPrompt(val); return this; } public TerminalInteraction WithPrompt(string prompt) { Prompt = ScriptableObject.CreateInstance<TerminalNode>(); Prompt.WithDisplayText(prompt); return this; } public TerminalInteraction WithHandler(Delegate handler) { Handlers.Add(handler); return this; } public object? HandleTerminalResponse(ArgumentStream arguments) { (from x in Handlers select (x.GetMethodInfo(), 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<object, object> invoker) && invoker != null) { object obj = invoker(handler.Target); if (obj != null) { return obj; } } } return null; } } } namespace LethalAPI.LibTerminal.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.LibTerminal.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() { if (CommandHandler.CurrentInterface != null) { return CommandHandler.CurrentInterface.GetType() == InterfaceType; } return false; } } public sealed class RequireInterfaceAttribute<T> : 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; } } }