using UnityEngine; using UnityEditor; using UnityEditor.Build.Reporting; using Newtonsoft.Json.Linq; using System; using System.Reflection; using System.Collections.Generic; using System.Linq; // Add LINQ namespace for Select extension method using System.Globalization; /// /// Handles editor control commands like undo, redo, play, pause, stop, and build operations. /// public static class EditorControlHandler { /// /// Handles editor control commands /// public static object HandleEditorControl(JObject @params) { string command = (string)@params["command"]; JObject commandParams = (JObject)@params["params"]; switch (command.ToUpper()) { case "UNDO": return HandleUndo(); case "REDO": return HandleRedo(); case "PLAY": return HandlePlay(); case "PAUSE": return HandlePause(); case "STOP": return HandleStop(); case "BUILD": return HandleBuild(commandParams); case "EXECUTE_COMMAND": return HandleExecuteCommand(commandParams); case "READ_CONSOLE": return ReadConsole(commandParams); case "GET_AVAILABLE_COMMANDS": return GetAvailableCommands(); default: return new { error = $"Unknown editor control command: {command}" }; } } private static object HandleUndo() { Undo.PerformUndo(); return new { message = "Undo performed successfully" }; } private static object HandleRedo() { Undo.PerformRedo(); return new { message = "Redo performed successfully" }; } private static object HandlePlay() { if (!EditorApplication.isPlaying) { EditorApplication.isPlaying = true; return new { message = "Entered play mode" }; } return new { message = "Already in play mode" }; } private static object HandlePause() { if (EditorApplication.isPlaying) { EditorApplication.isPaused = !EditorApplication.isPaused; return new { message = EditorApplication.isPaused ? "Game paused" : "Game resumed" }; } return new { message = "Not in play mode" }; } private static object HandleStop() { if (EditorApplication.isPlaying) { EditorApplication.isPlaying = false; return new { message = "Exited play mode" }; } return new { message = "Not in play mode" }; } private static object HandleBuild(JObject @params) { string platform = (string)@params["platform"]; string buildPath = (string)@params["buildPath"]; try { BuildTarget target = GetBuildTarget(platform); if ((int)target == -1) { return new { error = $"Unsupported platform: {platform}" }; } BuildPlayerOptions buildPlayerOptions = new BuildPlayerOptions(); buildPlayerOptions.scenes = GetEnabledScenes(); buildPlayerOptions.target = target; buildPlayerOptions.locationPathName = buildPath; BuildReport report = BuildPipeline.BuildPlayer(buildPlayerOptions); return new { message = "Build completed successfully", summary = report.summary }; } catch (System.Exception e) { return new { error = $"Build failed: {e.Message}" }; } } private static object HandleExecuteCommand(JObject @params) { string commandName = (string)@params["commandName"]; try { EditorApplication.ExecuteMenuItem(commandName); return new { message = $"Executed command: {commandName}" }; } catch (System.Exception e) { return new { error = $"Failed to execute command: {e.Message}" }; } } /// /// Reads log messages from the Unity Console /// /// Parameters containing filtering options /// Object containing console messages filtered by type public static object ReadConsole(JObject @params) { // Default values for show flags bool showLogs = true; bool showWarnings = true; bool showErrors = true; string searchTerm = string.Empty; // Get filter parameters if provided if (@params != null) { if (@params["show_logs"] != null) showLogs = (bool)@params["show_logs"]; if (@params["show_warnings"] != null) showWarnings = (bool)@params["show_warnings"]; if (@params["show_errors"] != null) showErrors = (bool)@params["show_errors"]; if (@params["search_term"] != null) searchTerm = (string)@params["search_term"]; } try { // Get required types and methods via reflection Type logEntriesType = Type.GetType("UnityEditor.LogEntries,UnityEditor"); Type logEntryType = Type.GetType("UnityEditor.LogEntry,UnityEditor"); if (logEntriesType == null || logEntryType == null) return new { error = "Could not find required Unity logging types", entries = new List() }; // Get essential methods MethodInfo getCountMethod = logEntriesType.GetMethod( "GetCount", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic ); MethodInfo getEntryMethod = logEntriesType.GetMethod( "GetEntryAt", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic ) ?? logEntriesType.GetMethod( "GetEntryInternal", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic ); if (getCountMethod == null || getEntryMethod == null) return new { error = "Could not find required Unity logging methods", entries = new List() }; // Get stack trace method if available MethodInfo getStackTraceMethod = logEntriesType.GetMethod( "GetEntryStackTrace", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, new[] { typeof(int) }, null ) ?? logEntriesType.GetMethod( "GetStackTrace", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, new[] { typeof(int) }, null ); // Get entry count and prepare result list int count = (int)getCountMethod.Invoke(null, null); var entries = new List(); // Create LogEntry instance to populate object logEntryInstance = Activator.CreateInstance(logEntryType); // Find properties on LogEntry type PropertyInfo modeProperty = logEntryType.GetProperty("mode") ?? logEntryType.GetProperty("Mode"); PropertyInfo messageProperty = logEntryType.GetProperty("message") ?? logEntryType.GetProperty("Message"); // Parse search terms if provided string[] searchWords = !string.IsNullOrWhiteSpace(searchTerm) ? searchTerm .ToLower() .Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries) : null; // Process each log entry for (int i = 0; i < count; i++) { try { // Get log entry at index i var methodParams = getEntryMethod.GetParameters(); if (methodParams.Length == 2 && methodParams[1].ParameterType == logEntryType) { getEntryMethod.Invoke(null, new object[] { i, logEntryInstance }); } else if ( methodParams.Length >= 1 && methodParams[0].ParameterType == typeof(int) ) { var parameters = new object[methodParams.Length]; parameters[0] = i; for (int p = 1; p < parameters.Length; p++) { parameters[p] = methodParams[p].ParameterType.IsValueType ? Activator.CreateInstance(methodParams[p].ParameterType) : null; } getEntryMethod.Invoke(null, parameters); } else continue; // Extract log data int logType = modeProperty != null ? Convert.ToInt32(modeProperty.GetValue(logEntryInstance) ?? 0) : 0; string message = messageProperty != null ? (messageProperty.GetValue(logEntryInstance)?.ToString() ?? "") : ""; // If message is empty, try to get it via a field if (string.IsNullOrEmpty(message)) { var msgField = logEntryType.GetField( "message", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance ); if (msgField != null) { object msgValue = msgField.GetValue(logEntryInstance); message = msgValue != null ? msgValue.ToString() : ""; } // If still empty, try alternate approach with Console window if (string.IsNullOrEmpty(message)) { // Access ConsoleWindow and its data Type consoleWindowType = Type.GetType( "UnityEditor.ConsoleWindow,UnityEditor" ); if (consoleWindowType != null) { try { // Get Console window instance var getWindowMethod = consoleWindowType.GetMethod( "GetWindow", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, new[] { typeof(bool) }, null ) ?? consoleWindowType.GetMethod( "GetConsoleWindow", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic ); if (getWindowMethod != null) { object consoleWindow = getWindowMethod.Invoke( null, getWindowMethod.GetParameters().Length > 0 ? new object[] { false } : null ); if (consoleWindow != null) { // Try to find log entries collection foreach ( var prop in consoleWindowType.GetProperties( BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic ) ) { if ( prop.PropertyType.IsArray || ( prop.PropertyType.IsGenericType && prop.PropertyType.GetGenericTypeDefinition() == typeof(List<>) ) ) { try { var logItems = prop.GetValue(consoleWindow); if (logItems != null) { if ( logItems.GetType().IsArray && i < ((Array)logItems).Length ) { var entry = ( (Array)logItems ).GetValue(i); if (entry != null) { var entryType = entry.GetType(); var entryMessageProp = entryType.GetProperty( "message" ) ?? entryType.GetProperty( "Message" ); if (entryMessageProp != null) { object value = entryMessageProp.GetValue( entry ); if (value != null) { message = value.ToString(); break; } } } } } } catch { // Ignore errors in this fallback approach } } } } } } catch { // Ignore errors in this fallback approach } } } // If still empty, try one more approach with log files if (string.IsNullOrEmpty(message)) { // This is our last resort - try to get log messages from the most recent Unity log file try { string logPath = string.Empty; // Determine the log file path based on the platform if (Application.platform == RuntimePlatform.WindowsEditor) { logPath = System.IO.Path.Combine( Environment.GetFolderPath( Environment.SpecialFolder.LocalApplicationData ), "Unity", "Editor", "Editor.log" ); } else if (Application.platform == RuntimePlatform.OSXEditor) { logPath = System.IO.Path.Combine( Environment.GetFolderPath( Environment.SpecialFolder.Personal ), "Library", "Logs", "Unity", "Editor.log" ); } else if (Application.platform == RuntimePlatform.LinuxEditor) { logPath = System.IO.Path.Combine( Environment.GetFolderPath( Environment.SpecialFolder.Personal ), ".config", "unity3d", "logs", "Editor.log" ); } if ( !string.IsNullOrEmpty(logPath) && System.IO.File.Exists(logPath) ) { // Read last few lines from the log file var logLines = ReadLastLines(logPath, 100); if (logLines.Count > i) { message = logLines[logLines.Count - 1 - i]; } } } catch { // Ignore errors in this fallback approach } } } // Get stack trace if method available string stackTrace = ""; if (getStackTraceMethod != null) { stackTrace = getStackTraceMethod.Invoke(null, new object[] { i })?.ToString() ?? ""; } // Filter by type bool typeMatch = (logType == 0 && showLogs) || (logType == 1 && showWarnings) || (logType == 2 && showErrors); if (!typeMatch) continue; // Filter by search term bool searchMatch = true; if (searchWords != null && searchWords.Length > 0) { string lowerMessage = message.ToLower(); string lowerStackTrace = stackTrace.ToLower(); foreach (string word in searchWords) { if (!lowerMessage.Contains(word) && !lowerStackTrace.Contains(word)) { searchMatch = false; break; } } } if (!searchMatch) continue; // Add matching entry to results string typeStr = logType == 0 ? "Log" : logType == 1 ? "Warning" : "Error"; entries.Add( new { type = typeStr, message = message, stackTrace = stackTrace } ); } catch (Exception) { // Skip entries that cause errors continue; } } // Return filtered results return new { message = "Console logs retrieved successfully", entries = entries, total_entries = count, filtered_count = entries.Count, show_logs = showLogs, show_warnings = showWarnings, show_errors = showErrors }; } catch (Exception e) { return new { error = $"Failed to read console logs: {e.Message}", entries = new List() }; } } private static MethodInfo FindMethod(Type type, string[] methodNames) { foreach (var methodName in methodNames) { var method = type.GetMethod( methodName, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic ); if (method != null) return method; } return null; } private static BuildTarget GetBuildTarget(string platform) { BuildTarget target; switch (platform.ToLower()) { case "windows": target = BuildTarget.StandaloneWindows64; break; case "mac": target = BuildTarget.StandaloneOSX; break; case "linux": target = BuildTarget.StandaloneLinux64; break; case "android": target = BuildTarget.Android; break; case "ios": target = BuildTarget.iOS; break; case "webgl": target = BuildTarget.WebGL; break; default: target = (BuildTarget)(-1); break; // Invalid target } return target; } private static string[] GetEnabledScenes() { var scenes = new System.Collections.Generic.List(); for (int i = 0; i < EditorBuildSettings.scenes.Length; i++) { if (EditorBuildSettings.scenes[i].enabled) { scenes.Add(EditorBuildSettings.scenes[i].path); } } return scenes.ToArray(); } /// /// Helper method to get information about available properties and fields in a type /// private static Dictionary GetTypeInfo(Type type) { var result = new Dictionary(); // Get all public and non-public properties var properties = type.GetProperties( BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance ); var propList = new List(); foreach (var prop in properties) { propList.Add($"{prop.PropertyType.Name} {prop.Name}"); } result["Properties"] = propList; // Get all public and non-public fields var fields = type.GetFields( BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance ); var fieldList = new List(); foreach (var field in fields) { fieldList.Add($"{field.FieldType.Name} {field.Name}"); } result["Fields"] = fieldList; // Get all public and non-public methods var methods = type.GetMethods( BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance ); var methodList = new List(); foreach (var method in methods) { if (!method.Name.StartsWith("get_") && !method.Name.StartsWith("set_")) { var parameters = string.Join( ", ", method.GetParameters().Select(p => $"{p.ParameterType.Name} {p.Name}") ); methodList.Add($"{method.ReturnType.Name} {method.Name}({parameters})"); } } result["Methods"] = methodList; return result; } /// /// Helper method to get all property and field values from an object /// private static Dictionary GetObjectValues(object obj) { if (obj == null) return new Dictionary(); var result = new Dictionary(); var type = obj.GetType(); // Get all property values var properties = type.GetProperties( BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance ); foreach (var prop in properties) { try { var value = prop.GetValue(obj); result[$"Property:{prop.Name}"] = value?.ToString() ?? "null"; } catch (Exception) { result[$"Property:{prop.Name}"] = "ERROR"; } } // Get all field values var fields = type.GetFields( BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance ); foreach (var field in fields) { try { var value = field.GetValue(obj); result[$"Field:{field.Name}"] = value?.ToString() ?? "null"; } catch (Exception) { result[$"Field:{field.Name}"] = "ERROR"; } } return result; } /// /// Reads the last N lines from a file /// private static List ReadLastLines(string filePath, int lineCount) { var result = new List(); using ( var stream = new System.IO.FileStream( filePath, System.IO.FileMode.Open, System.IO.FileAccess.Read, System.IO.FileShare.ReadWrite ) ) using (var reader = new System.IO.StreamReader(stream)) { string line; var circularBuffer = new List(lineCount); int currentIndex = 0; // Read all lines keeping only the last N in a circular buffer while ((line = reader.ReadLine()) != null) { if (circularBuffer.Count < lineCount) { circularBuffer.Add(line); } else { circularBuffer[currentIndex] = line; currentIndex = (currentIndex + 1) % lineCount; } } // Reorder the circular buffer so that lines are returned in order if (circularBuffer.Count == lineCount) { for (int i = 0; i < lineCount; i++) { result.Add(circularBuffer[(currentIndex + i) % lineCount]); } } else { result.AddRange(circularBuffer); } } return result; } /// /// Gets a list of available editor commands that can be executed /// the method should have a MenuItem attribute /// /// Object containing list of available command paths [MenuItem("Window/Get Available Commands")] private static object GetAvailableCommands() { var commands = new List(); Assembly assembly = Assembly.GetExecutingAssembly(); foreach (Type type in assembly.GetTypes()) { MethodInfo[] methods = type.GetMethods( BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic ); foreach (MethodInfo method in methods) { // Look for the MenuItem attribute object[] attributes = method.GetCustomAttributes( typeof(UnityEditor.MenuItem), false ); if (attributes.Length > 0) { UnityEditor.MenuItem menuItem = attributes[0] as UnityEditor.MenuItem; commands.Add(menuItem.menuItem); } } } Debug.Log($"commands.Count: {commands.Count}"); foreach (var command in commands) { Debug.Log($"Command: {command}"); } return new { commands = commands }; } }