using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using RebootKit.Engine.Foundation; using UnityEngine; using Logger = RebootKit.Engine.Foundation.Logger; namespace RebootKit.Engine.Services.Console { public interface IConsoleCommand { string Name { get; } string Description { get; } void Execute(string[] args); } public class HelpCommand : IConsoleCommand { public string Name { get; } = "help"; public string Description { get; } = "Prints available commands/cvars and their descriptions."; public void Execute(string[] args) { RR.Console.PrintHelp(); } } public class PrintCVarsCommand : IConsoleCommand { public string Name { get; } = "print_cvars"; public string Description { get; } = "Prints all cvars and their values."; public void Execute(string[] args) { RR.Console.PrintCVars(); } } public class ConsoleService : IService { static readonly Logger s_logger = new(nameof(ConsoleService)); readonly List m_ConsoleCommands = new(); FileStream m_LogFileStream; TextWriter m_LogFileWriter; bool m_IsLoading; public ConsoleService() { ConfigVar.StateChanged += OnCVarStateChanged; m_LogFileStream = new FileStream(Application.persistentDataPath + "/rr_logs.txt", FileMode.Append, FileAccess.Write); m_LogFileWriter = new StreamWriter(m_LogFileStream); m_LogFileWriter.WriteLine("============================"); m_LogFileWriter.WriteLine("Starting new log"); m_LogFileWriter.WriteLine($" > Game: {Application.productName}"); m_LogFileWriter.WriteLine($" > Version: {Application.version}"); m_LogFileWriter.WriteLine($" > Date: {DateTime.Now}"); m_LogFileWriter.WriteLine("============================"); m_LogFileWriter.Flush(); s_logger.Info("Waking up"); Load(); RegisterCommand(new PrintCVarsCommand()); RegisterCommand(new HelpCommand()); } public void Dispose() { s_logger.Info("Shutting down"); ConfigVar.StateChanged -= OnCVarStateChanged; m_LogFileWriter.Dispose(); m_LogFileStream.Dispose(); m_LogFileStream = null; m_LogFileWriter = null; } void OnCVarStateChanged(ConfigVar cvar) { if (m_IsLoading) { return; } if (!cvar.flags.HasFlag(CVarFlags.ReadOnly)) { Save(); } } public event Action OnOutputMessage = _ => { }; public void WriteToOutput(string message) { if (m_LogFileWriter != null) { m_LogFileWriter.WriteLine(message); m_LogFileWriter.Flush(); } OnOutputMessage?.Invoke(message); } string[] ParseCommandInputArguments(string text) { if (text.Length == 0) { return Array.Empty(); } return new[] {text}; } public void Execute(string input) { if (input.Length == 0) { return; } string commandName = input; if (input.IndexOf(' ') != -1) { commandName = input.Substring(0, input.IndexOf(' ')); } string[] arguments = ParseCommandInputArguments(input.Substring(commandName.Length)); foreach (IConsoleCommand command in m_ConsoleCommands) { if (command.Name.Equals(commandName)) { command.Execute(arguments); return; } } foreach (ConfigVar cvar in ConfigVarsContainer.All()) { if (cvar.name.Equals(commandName)) { if (arguments.Length == 1) { cvar.ParseFromString(arguments[0]); } WriteToOutput($"{cvar.name} - {cvar}\n"); return; } } WriteToOutput($"ERROR: Command/CVar `{commandName}` not found."); } public void RegisterCommand(IConsoleCommand command) { if (m_ConsoleCommands.Any(t => t.Name.Equals(command.Name))) { s_logger.Error($"`{command.Name}` command is already registered"); return; } m_ConsoleCommands.Add(command); s_logger.Info($"Registered command: {command.Name}"); } public void PrintHelp() { StringBuilder message = new(); message.AppendLine("Available commands:"); foreach (IConsoleCommand command in m_ConsoleCommands) { message.Append(" "); message.Append(command.Name); message.Append(" - "); message.Append(command.Description); message.AppendLine(); } message.AppendLine("Available cvars:"); foreach (ConfigVar cvar in ConfigVarsContainer.All()) { message.Append(" "); message.Append(cvar.name); message.Append(" - "); message.Append(cvar.description); message.AppendLine(); } WriteToOutput(message.ToString()); } public void PrintCVars() { StringBuilder message = new(); foreach (ConfigVar cvar in ConfigVarsContainer.All()) { message.AppendLine($"{cvar.name} - {cvar}"); } WriteToOutput(message.ToString()); } void Save() { string path = Application.persistentDataPath + "/" + RConsts.k_CVarsFilename; s_logger.Info("Saving cvars to file: " + path); StringBuilder sb = new(); foreach (ConfigVar cvar in ConfigVarsContainer.All()) { if (!cvar.flags.HasFlag(CVarFlags.ReadOnly)) { sb.AppendFormat("{0} {1}\n", cvar.name, cvar); } } File.WriteAllText(path, sb.ToString()); } void Load() { string path = Application.persistentDataPath + "/" + RConsts.k_CVarsFilename; if (!File.Exists(path)) { s_logger.Info("CVar file not found, skipping load"); return; } m_IsLoading = true; ExecuteFile(path); m_IsLoading = false; } bool ExecuteFile(string path) { if (!File.Exists(path)) { s_logger.Error($"Cannot load file '{path}', file not found"); return false; } s_logger.Info($"Executing file '{path}'"); string[] lines = File.ReadAllLines(path); foreach (string line in lines) { WriteToOutput(path + " > " + line); Execute(line); } return true; } } }