using System; using System.Collections; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; using System.Threading; using HG; using HG.Reflection; using RoR2.ConVar; using UnityEngine; using UnityEngine.Networking; namespace RoR2; public class Console : MonoBehaviour { public struct Log { public string message; public string stackTrace; public LogType logType; } public delegate void LogReceivedDelegate(Log log); private class Lexer { private enum TokenType { Identifier, NestedString } private string srcString; private int readIndex; private StringBuilder stringBuilder = new StringBuilder(); public Lexer(string srcString) { this.srcString = srcString; readIndex = 0; } private static bool IsIgnorableCharacter(char character) { if (!IsSeparatorCharacter(character) && !IsQuoteCharacter(character) && !IsIdentifierCharacter(character)) { return character != '/'; } return false; } private static bool IsSeparatorCharacter(char character) { if (character != ';') { return character == '\n'; } return true; } private static bool IsQuoteCharacter(char character) { if (character != '\'') { return character == '"'; } return true; } private static bool IsIdentifierCharacter(char character) { if (!char.IsLetterOrDigit(character) && character != '_' && character != '.' && character != '-') { return character == ':'; } return true; } private bool TrimComment() { if (readIndex >= srcString.Length) { return false; } if (srcString[readIndex] == '/') { if (readIndex + 1 < srcString.Length) { char c = srcString[readIndex + 1]; if (c == '/') { while (readIndex < srcString.Length) { if (srcString[readIndex] == '\n') { readIndex++; return true; } readIndex++; } return true; } if (c == '*') { while (readIndex < srcString.Length - 1) { if (srcString[readIndex] == '*' && srcString[readIndex + 1] == '/') { readIndex += 2; return true; } readIndex++; } return true; } } readIndex++; } return false; } private void TrimWhitespace() { while (readIndex < srcString.Length && IsIgnorableCharacter(srcString[readIndex])) { readIndex++; } } private void TrimUnused() { do { TrimWhitespace(); } while (TrimComment()); } private static int UnescapeNext(string srcString, int startPos, out char result) { result = '\\'; int num = startPos; num++; if (num < srcString.Length) { char c = srcString[num]; switch (c) { case '"': case '\'': case '\\': result = c; return 2; case 'n': result = '\n'; return 2; } } return 1; } public string NextToken() { TrimUnused(); if (readIndex == srcString.Length) { return null; } TokenType tokenType = TokenType.Identifier; char c = srcString[readIndex]; char c2 = '\0'; if (IsQuoteCharacter(c)) { tokenType = TokenType.NestedString; c2 = c; readIndex++; } else if (IsSeparatorCharacter(c)) { readIndex++; return ";"; } char result; for (; readIndex < srcString.Length; stringBuilder.Append(result), readIndex++) { result = srcString[readIndex]; switch (tokenType) { case TokenType.Identifier: if (IsIdentifierCharacter(result)) { continue; } break; case TokenType.NestedString: if (result == '\\') { readIndex += UnescapeNext(srcString, readIndex, out result) - 1; continue; } if (result != c2) { continue; } readIndex++; break; default: continue; } break; } string result2 = stringBuilder.ToString(); stringBuilder.Length = 0; return result2; } public Queue GetTokens() { Queue queue = new Queue(); for (string text = NextToken(); text != null; text = NextToken()) { queue.Enqueue(text); } queue.Enqueue(";"); return queue; } } private class Substring { public string srcString; public int startIndex; public int length; public int endIndex => startIndex + length; public string str => srcString.Substring(startIndex, length); public Substring nextToken => new Substring { srcString = srcString, startIndex = startIndex + length, length = 0 }; } private class ConCommand { public ConVarFlags flags; public ConCommandDelegate action; public string helpText; } public delegate void ConCommandDelegate(ConCommandArgs args); private class ConvarClass { public string containerAssemblyName; public List convars = new List(); } public readonly struct CmdSender { public readonly LocalUser localUser; public readonly NetworkUser networkUser; public CmdSender(LocalUser localUser) { this.localUser = localUser; networkUser = localUser?.currentNetworkUser; } public CmdSender(NetworkUser networkUser) { localUser = networkUser?.localUser; this.networkUser = networkUser; } public static implicit operator CmdSender(LocalUser localUser) { return new CmdSender(localUser); } public static implicit operator CmdSender(NetworkUser networkUser) { return new CmdSender(networkUser); } } private enum SystemConsoleType { None, Attach, Alloc } public class AutoComplete { private struct MatchInfo { public string str; public int similarity; } private List searchableStrings = new List(); private string searchString; public List resultsList = new List(); public List GetSearchableStrings => searchableStrings; public AutoComplete(Console console) { HashSet hashSet = new HashSet(); for (int i = 0; i < userCmdHistory.Count; i++) { hashSet.Add(userCmdHistory[i]); } foreach (KeyValuePair allConVar in console.allConVars) { hashSet.Add(allConVar.Key); } foreach (KeyValuePair item in console.concommandCatalog) { hashSet.Add(item.Key); } foreach (string item2 in hashSet) { searchableStrings.Add(item2); } searchableStrings.Sort(); } public bool SetSearchString(string newSearchString) { newSearchString = newSearchString.ToLower(CultureInfo.InvariantCulture); if (newSearchString == searchString) { return false; } searchString = newSearchString; List list = new List(); for (int i = 0; i < searchableStrings.Count; i++) { string text = searchableStrings[i]; int num = Math.Min(text.Length, searchString.Length); int j; for (j = 0; j < num && char.ToLower(text[j]) == searchString[j]; j++) { } if (j > 1) { list.Add(new MatchInfo { str = text, similarity = j }); } } list.Sort(delegate(MatchInfo a, MatchInfo b) { if (a.similarity == b.similarity) { return string.CompareOrdinal(a.str, b.str); } return (a.similarity <= b.similarity) ? 1 : (-1); }); resultsList = new List(); for (int k = 0; k < list.Count; k++) { resultsList.Add(list[k].str); } return true; } } public class CheatsConVar : BaseConVar { public static readonly CheatsConVar instance = new CheatsConVar("cheats", ConVarFlags.ExecuteOnServer, "0", "Enable cheats. Achievements, unlock progression, and stat tracking will be disabled until the application is restarted."); private bool _boolValue; public bool boolValue { get { return _boolValue; } private set { if (_boolValue) { sessionCheatsEnabled = true; } } } public CheatsConVar(string name, ConVarFlags flags, string defaultValue, string helpText) : base(name, flags, defaultValue, helpText) { } public override void SetString(string newValue) { if (TextSerialization.TryParseInvariant(newValue, out int result)) { boolValue = result != 0; } } public override string GetString() { if (!boolValue) { return "0"; } return "1"; } } public static List logs = new List(); private Dictionary vstrs = new Dictionary(); private Dictionary concommandCatalog = new Dictionary(); private Dictionary allConVars; private List archiveConVars; public static List userCmdHistory = new List(); private const int VK_RETURN = 13; private const int WM_KEYDOWN = 256; private static byte[] inputStreamBuffer = new byte[256]; private static Queue stdInQueue = new Queue(); private static Thread stdInReaderThread = null; private static SystemConsoleType systemConsoleType = SystemConsoleType.None; public static bool loadingIsComplete = false; private int maxPassesBeforeYielding = 25; private static readonly StringBuilder sharedStringBuilder = new StringBuilder(); private const string configFolder = "/Config/"; private const string archiveConVarsPath = "/Config/config.cfg"; private static IntConVar maxMessages = new IntConVar("max_messages", ConVarFlags.Archive, "25", "Maximum number of messages that can be held in the console log."); public static Console instance { get; private set; } public static bool sessionCheatsEnabled { get; private set; } = false; public static event LogReceivedDelegate onLogReceived; public static event Action onClear; [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] private static void RegisterLogHandler() { Application.logMessageReceived += HandleLog; } private static void HandleLog(string message, string stackTrace, LogType logType) { switch (logType) { case LogType.Error: message = string.Format(CultureInfo.InvariantCulture, "{0}", message); break; case LogType.Warning: message = string.Format(CultureInfo.InvariantCulture, "{0}", message); break; } Log log = default(Log); log.message = message; log.stackTrace = stackTrace; log.logType = logType; Log log2 = log; logs.Add(log2); if (maxMessages.value > 0) { while (logs.Count > maxMessages.value) { logs.RemoveAt(0); } } if (Console.onLogReceived != null) { Console.onLogReceived(log2); } } private string GetVstrValue(LocalUser user, string identifier) { if (user == null) { if (vstrs.TryGetValue(identifier, out var value)) { return value; } return ""; } return ""; } public void CacheConVars() { _ = typeof(BaseConVar).Assembly; List list = new List(); Type[] types = typeof(BaseConVar).Assembly.GetTypes(); foreach (Type type in types) { bool flag = false; FieldInfo[] fields = type.GetFields(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); foreach (FieldInfo fieldInfo in fields) { if (!fieldInfo.FieldType.IsSubclassOf(typeof(BaseConVar))) { continue; } if (fieldInfo.IsStatic) { BaseConVar conVar = (BaseConVar)fieldInfo.GetValue(null); RegisterConVarInternal(conVar); if (!flag) { flag = true; list.Add(new ConvarClass { containerAssemblyName = type.FullName }); } list[list.Count - 1].convars.Add(fieldInfo.Name); } else if (CustomAttributeExtensions.GetCustomAttribute(type) == null) { Debug.LogErrorFormat("ConVar defined as {0}.{1} could not be registered. ConVars must be static fields.", type.Name, fieldInfo.Name); } } MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo in methods) { if (CustomAttributeExtensions.GetCustomAttribute(methodInfo) == null) { continue; } Debug.LogErrorFormat("ConVarProviderAttribute on method {0}.{1}", type.Name, methodInfo.Name); if (methodInfo.ReturnType != typeof(IEnumerable) || methodInfo.GetParameters().Length != 0) { Debug.LogErrorFormat("ConVar provider {0}.{1} does not match the signature \"static IEnumerable()\".", type.Name, methodInfo.Name); continue; } if (!methodInfo.IsStatic) { Debug.LogErrorFormat("ConVar provider {0}.{1} could not be invoked. Methods marked with the ConVarProvider attribute must be static.", type.Name, methodInfo.Name); continue; } foreach (BaseConVar item in (IEnumerable)methodInfo.Invoke(null, Array.Empty())) { RegisterConVarInternal(item); } } } if (list.Count <= 0) { return; } StreamWriter streamWriter = new StreamWriter(Application.streamingAssetsPath + "/ConVarNames.txt"); foreach (ConvarClass item2 in list) { streamWriter.WriteLine(item2.containerAssemblyName); streamWriter.WriteLine(item2.convars.Count); foreach (string convar in item2.convars) { streamWriter.WriteLine(convar); } } streamWriter.Close(); } private IEnumerator InternalInitConVarsCoroutine() { CacheConVars(); yield return null; string path = Application.streamingAssetsPath + "/ConVarNames.txt"; string[] fileLines; int curLine; if (File.Exists(path)) { fileLines = File.ReadAllLines(path); curLine = 0; StringBuilder sb = new StringBuilder(256); string assemblyName = typeof(BaseConVar).Assembly.FullName; int currentPass = 0; while (!EndOfStream()) { string text = ReadLine(); if (!string.IsNullOrEmpty(text)) { int num = int.Parse(ReadLine()); sb.Clear(); sb.AppendFormat("{0}, {1}", text, assemblyName); Type type = Type.GetType(sb.ToString()); if (type != null) { for (int i = 0; i < num; i++) { string text2 = ReadLine(); FieldInfo field = type.GetField(text2, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { BaseConVar conVar = (BaseConVar)field.GetValue(null); RegisterConVarInternal(conVar); } else { Debug.LogError("Failed to find convar " + text2); } } } else { Debug.LogErrorFormat("Failed to get type {0} (Full name: {1})", text, sb.ToString()); for (int j = 0; j < num; j++) { ReadLine(); } } } currentPass++; if (currentPass >= maxPassesBeforeYielding) { currentPass = 0; yield return null; } } } else { Debug.LogError("ERROR: Failed to load StreamingAssets/ConVarNames.txt"); } bool EndOfStream() { return curLine >= fileLines.Length; } string ReadLine() { return fileLines[curLine++]; } } private void InternalInitConVars() { string path = Application.streamingAssetsPath + "/ConVarNames.txt"; string[] fileLines; int curLine; if (File.Exists(path)) { fileLines = File.ReadAllLines(path); curLine = 0; StringBuilder stringBuilder = new StringBuilder(256); string fullName = typeof(BaseConVar).Assembly.FullName; while (!EndOfStream()) { string text = ReadLine(); if (string.IsNullOrEmpty(text)) { continue; } int num = int.Parse(ReadLine()); stringBuilder.Clear(); stringBuilder.AppendFormat("{0}, {1}", text, fullName); Type type = Type.GetType(stringBuilder.ToString()); if (type != null) { for (int i = 0; i < num; i++) { string text2 = ReadLine(); FieldInfo field = type.GetField(text2, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { BaseConVar conVar = (BaseConVar)field.GetValue(null); RegisterConVarInternal(conVar); } else { Debug.LogError("Failed to find convar " + text2); } } } else { Debug.LogErrorFormat("Failed to get type {0} (Full name: {1})", text, stringBuilder.ToString()); for (int j = 0; j < num; j++) { ReadLine(); } } } } else { Debug.LogError("ERROR: Failed to load StreamingAssets/ConVarNames.txt"); } bool EndOfStream() { return curLine >= fileLines.Length; } string ReadLine() { return fileLines[curLine++]; } } public IEnumerator InitConVarsCoroutine() { allConVars = new Dictionary(); archiveConVars = new List(); yield return InternalInitConVarsCoroutine(); int currentPass = 0; foreach (KeyValuePair allConVar in allConVars) { try { BaseConVar value = allConVar.Value; if ((value.flags & ConVarFlags.Engine) != 0) { value.defaultValue = value.GetString(); } else if (value.defaultValue != null) { value.AttemptSetString(value.defaultValue); } } catch (Exception message) { Debug.LogError(message); } currentPass++; if (currentPass >= maxPassesBeforeYielding) { currentPass = 0; yield return null; } } } public void InitConVars() { allConVars = new Dictionary(); archiveConVars = new List(); InternalInitConVars(); foreach (KeyValuePair allConVar in allConVars) { try { BaseConVar value = allConVar.Value; if ((value.flags & ConVarFlags.Engine) != 0) { value.defaultValue = value.GetString(); } else if (value.defaultValue != null) { value.AttemptSetString(value.defaultValue); } } catch (Exception message) { Debug.LogError(message); } } } private void RegisterConVarInternal(BaseConVar conVar) { if (conVar == null) { Debug.LogWarning("Attempted to register null ConVar"); return; } allConVars[conVar.name] = conVar; if ((conVar.flags & ConVarFlags.Archive) != 0) { archiveConVars.Add(conVar); } } public BaseConVar FindConVar(string name) { if (allConVars.TryGetValue(name, out var value)) { return value; } return null; } public void SubmitCmd(NetworkUser sender, string cmd, bool recordSubmit = false) { SubmitCmd((CmdSender)sender, cmd, recordSubmit); } public void SubmitCmd(CmdSender sender, string cmd, bool recordSubmit = false) { if (recordSubmit) { Log log = default(Log); log.message = string.Format(CultureInfo.InvariantCulture, "] {0}", cmd); log.stackTrace = ""; log.logType = LogType.Log; Log log2 = log; logs.Add(log2); if (Console.onLogReceived != null) { Console.onLogReceived(log2); } userCmdHistory.Add(cmd); } Queue tokens = new Lexer(cmd).GetTokens(); List list = new List(); bool flag = false; while (tokens.Count != 0) { string text = tokens.Dequeue(); if (text == ";") { flag = false; if (list.Count > 0) { string concommandName = list[0].ToLower(CultureInfo.InvariantCulture); list.RemoveAt(0); RunCmd(sender, concommandName, list); list.Clear(); } } else { if (flag) { text = GetVstrValue(sender.localUser, text); flag = false; } if (text == "vstr") { flag = true; } else { list.Add(text); } } } } private void ForwardCmdToServer(ConCommandArgs args) { if ((bool)args.sender) { args.sender.CallCmdSendConsoleCommand(args.commandName, args.userArgs.ToArray()); } } public void RunClientCmd(NetworkUser sender, string concommandName, string[] args) { RunCmd(sender, concommandName, new List(args)); } private void RunCmd(CmdSender sender, string concommandName, List userArgs) { bool flag = !(sender.networkUser?.isLocalPlayer ?? true); ConVarFlags conVarFlags = ConVarFlags.None; ConCommand value = null; BaseConVar baseConVar = null; if (concommandCatalog.TryGetValue(concommandName, out value)) { conVarFlags = value.flags; } else { baseConVar = FindConVar(concommandName); if (baseConVar == null) { Debug.LogFormat("\"{0}\" is not a recognized ConCommand or ConVar.", concommandName); return; } conVarFlags = baseConVar.flags; } bool flag2 = (conVarFlags & ConVarFlags.ExecuteOnServer) != 0; if (!NetworkServer.active && flag2) { ForwardCmdToServer(new ConCommandArgs { sender = sender.networkUser, commandName = concommandName, userArgs = userArgs }); return; } if (flag && (conVarFlags & ConVarFlags.SenderMustBeServer) != 0) { Debug.LogFormat("Blocked server-only command {0} from remote user {1}.", concommandName, sender.networkUser.userName); return; } if (flag && !flag2) { Debug.LogFormat("Blocked non-transmittable command {0} from remote user {1}.", concommandName, sender.networkUser.userName); return; } if ((conVarFlags & ConVarFlags.Cheat) != 0 && !CheatsConVar.instance.boolValue) { Debug.LogFormat("Command \"{0}\" cannot be used while cheats are disabled.", concommandName); return; } if (value != null) { try { value.action(new ConCommandArgs { sender = sender.networkUser, localUserSender = sender.localUser, commandName = concommandName, userArgs = userArgs }); return; } catch (ConCommandException ex) { Debug.LogFormat("Command \"{0}\" failed: {1}", concommandName, ex.Message); return; } } if (baseConVar != null) { if (userArgs.Count > 0) { baseConVar.AttemptSetString(userArgs[0]); return; } Debug.LogFormat("\"{0}\" = \"{1}\"\n{2}", concommandName, baseConVar.GetString(), baseConVar.helpText); } } [DllImport("kernel32.dll")] private static extern bool AllocConsole(); [DllImport("kernel32.dll")] private static extern bool FreeConsole(); [DllImport("kernel32.dll")] private static extern bool AttachConsole(int processId); [DllImport("user32.dll")] private static extern bool PostMessage(IntPtr hWnd, uint msg, int wParam, int lParam); [DllImport("kernel32.dll")] private static extern IntPtr GetConsoleWindow(); private static string ReadInputStream() { if (stdInQueue.Count > 0) { return stdInQueue.Dequeue(); } return null; } private static void ThreadedInputQueue() { string item; while (systemConsoleType != 0 && (item = System.Console.ReadLine()) != null) { stdInQueue.Enqueue(item); } } private static void SetupSystemConsole() { bool flag = false; bool flag2 = false; string[] commandLineArgs = Environment.GetCommandLineArgs(); for (int i = 0; i < commandLineArgs.Length; i++) { if (commandLineArgs[i] == "-console") { flag = flag || true; } if (commandLineArgs[i] == "-console_detach") { flag2 = flag2 || true; } } if (flag) { systemConsoleType = SystemConsoleType.Attach; if (flag2) { systemConsoleType = SystemConsoleType.Alloc; } } switch (systemConsoleType) { case SystemConsoleType.Attach: AttachConsole(-1); break; case SystemConsoleType.Alloc: AllocConsole(); break; } if (systemConsoleType != 0) { System.Console.SetIn(new StreamReader(System.Console.OpenStandardInput())); stdInReaderThread = new Thread(ThreadedInputQueue); stdInReaderThread.Start(); } } private void Awake() { instance = this; loadingIsComplete = false; StartCoroutine(AwakeCoroutine()); } private IEnumerator AwakeCoroutine() { SetupSystemConsole(); yield return InitConVarsCoroutine(); List instances = HG.Reflection.SearchableAttribute.GetInstances(); int currentPass = 0; foreach (ConCommandAttribute item in instances) { concommandCatalog[item.commandName.ToLower(CultureInfo.InvariantCulture)] = new ConCommand { flags = item.flags, action = (ConCommandDelegate)Delegate.CreateDelegate(typeof(ConCommandDelegate), item.target as MethodInfo), helpText = item.helpText }; currentPass++; if (currentPass >= maxPassesBeforeYielding) { currentPass = 0; yield return null; } } string[] commandLineArgs = Environment.GetCommandLineArgs(); StringBuilder stringBuilder = HG.StringBuilderPool.RentStringBuilder(); stringBuilder.AppendLine("Launch Parameters: "); for (int i = 0; i < commandLineArgs.Length; i++) { stringBuilder.Append(" arg[").AppendInt(i).Append("]=\"") .Append(commandLineArgs[i]) .Append("\"") .AppendLine(); } HG.StringBuilderPool.ReturnStringBuilder(stringBuilder); MPEventSystemManager.availability.CallWhenAvailable(LoadStartupConfigs); loadingIsComplete = true; } private void LoadStartupConfigs() { try { SubmitCmd(null, "exec config"); SubmitCmd(null, "exec autoexec"); } catch (Exception message) { Debug.LogError(message); } } private void Update() { string cmd; while ((cmd = ReadInputStream()) != null) { SubmitCmd(null, cmd, recordSubmit: true); } } private void OnDestroy() { if (stdInReaderThread != null) { stdInReaderThread = null; } if (systemConsoleType != 0) { systemConsoleType = SystemConsoleType.None; IntPtr consoleWindow = GetConsoleWindow(); if (consoleWindow != IntPtr.Zero) { PostMessage(consoleWindow, 256u, 13, 0); } if (stdInReaderThread != null) { stdInReaderThread.Join(); stdInReaderThread = null; } FreeConsole(); } } private static string LoadConfig(string fileName) { string text = sharedStringBuilder.Clear().Append("/Config/").Append(fileName) .Append(".cfg") .ToString(); try { return PlatformSystems.textDataManager.GetConfFile(fileName, text); } catch (IOException ex) { Debug.LogFormat("Could not load config {0}: {1}", text, ex.Message); } return null; } public void SaveArchiveConVars() { using MemoryStream memoryStream = new MemoryStream(); using TextWriter textWriter = new StreamWriter(memoryStream, Encoding.UTF8); for (int i = 0; i < archiveConVars.Count; i++) { BaseConVar baseConVar = archiveConVars[i]; textWriter.Write(baseConVar.name); textWriter.Write(" "); textWriter.Write(baseConVar.GetString()); textWriter.Write(";\r\n"); } textWriter.Write("echo \"Loaded archived convars.\";"); textWriter.Flush(); RoR2Application.fileSystem.CreateDirectory("/Config/"); try { using Stream stream = RoR2Application.fileSystem.OpenFile("/Config/config.cfg", FileMode.Create, FileAccess.Write); if (stream != null) { stream.Write(memoryStream.GetBuffer(), 0, (int)memoryStream.Length); stream.Close(); } } catch (IOException ex) { Debug.LogFormat("Failed to write archived convars: {0}", ex.Message); } } [ConCommand(commandName = "set_vstr", flags = ConVarFlags.None, helpText = "Sets the specified vstr to the specified value.")] private static void CCSetVstr(ConCommandArgs args) { args.CheckArgumentCount(2); instance.vstrs.Add(args[0], args[1]); } [ConCommand(commandName = "exec", flags = ConVarFlags.None, helpText = "Executes a named config from the \"Config/\" folder.")] private static void CCExec(ConCommandArgs args) { if (args.Count > 0) { string text = LoadConfig(args[0]); if (text != null) { instance.SubmitCmd(args.sender, text); } } } [ConCommand(commandName = "echo", flags = ConVarFlags.None, helpText = "Echoes the given text to the console.")] private static void CCEcho(ConCommandArgs args) { if (args.Count <= 0) { ShowHelpText(args.commandName); } } [ConCommand(commandName = "cvarlist", flags = ConVarFlags.None, helpText = "Print all available convars and concommands.")] private static void CCCvarList(ConCommandArgs args) { List list = new List(); foreach (KeyValuePair allConVar in instance.allConVars) { list.Add(allConVar.Key); } foreach (KeyValuePair item in instance.concommandCatalog) { list.Add(item.Key); } list.Sort(); } [ConCommand(commandName = "help", flags = ConVarFlags.None, helpText = "Show help text for the named convar or concommand.")] private static void CCHelp(ConCommandArgs args) { if (args.Count == 0) { instance.SubmitCmd(args.sender, "find \"*\""); } else { ShowHelpText(args[0]); } } [ConCommand(commandName = "find", flags = ConVarFlags.None, helpText = "Find all concommands and convars with the specified substring.")] private static void CCFind(ConCommandArgs args) { if (args.Count == 0) { ShowHelpText("find"); } else { Find(args[0].ToLower(CultureInfo.InvariantCulture)); } } private static string Find(string searchString) { bool flag = searchString == "*"; List list = new List(); foreach (KeyValuePair allConVar in instance.allConVars) { if (flag || allConVar.Key.ToLower(CultureInfo.InvariantCulture).Contains(searchString) || allConVar.Value.helpText.ToLower(CultureInfo.InvariantCulture).Contains(searchString)) { list.Add(allConVar.Key); } } foreach (KeyValuePair item in instance.concommandCatalog) { if (flag || item.Key.ToLower(CultureInfo.InvariantCulture).Contains(searchString) || item.Value.helpText.ToLower(CultureInfo.InvariantCulture).Contains(searchString)) { list.Add(item.Key); } } list.Sort(); string[] array = new string[list.Count]; for (int i = 0; i < list.Count; i++) { array[i] = GetHelpText(list[i]); } return string.Join("\n", array); } [ConCommand(commandName = "clear", flags = ConVarFlags.None, helpText = "Clears the console output.")] private static void CCClear(ConCommandArgs args) { logs.Clear(); Console.onClear?.Invoke(); } private static string GetHelpText(string commandName) { if (instance.concommandCatalog.TryGetValue(commandName, out var value)) { return string.Format(CultureInfo.InvariantCulture, "\"{0}\"\n- {1}", commandName, value.helpText); } BaseConVar baseConVar = instance.FindConVar(commandName); if (baseConVar != null) { return string.Format(CultureInfo.InvariantCulture, "\"{0}\" = \"{1}\"\n - {2}", commandName, baseConVar.GetString(), baseConVar.helpText); } return Find(commandName) + "\nExact Match not found. Use `find \"" + commandName + "\" instead"; } public static void ShowHelpText(string commandName) { } }