using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Codely.Newtonsoft.Json.Linq; using UnityEngine; using UnityTcp.Editor.Helpers; using Microsoft.CodeAnalysis.CSharp.Scripting; using Microsoft.CodeAnalysis.Scripting; namespace UnityTcp.Editor.Tools { /// /// Executes C# scripts using Microsoft.CodeAnalysis.CSharp.Scripting (Roslyn). /// Captures and returns logs generated during script execution. /// Ensures execution happens on the main thread. /// public static class ExecuteCSharpScript { private static List _capturedLogs = new List(); private static bool _isCapturingLogs = false; /// /// Main handler for executing C# scripts. /// public static object HandleCommand(JObject @params) { string script = @params["script"]?.ToString(); if (string.IsNullOrEmpty(script)) { return Response.Error("'script' parameter is required."); } bool captureLogs = @params["capture_logs"]?.ToObject() ?? true; string[] imports = @params["imports"]?.ToObject() ?? new string[] { "System", "System.Linq", "System.Collections.Generic", "UnityEngine", "UnityEditor", "UnityEditor.SceneManagement", "UnityEngine.SceneManagement" }; try { Debug.Log($"[ExecuteCSharpScript] Executing C# script (length: {script.Length} chars)"); StartLogCapture(captureLogs); object result; try { result = ExecuteScriptInternal(script, imports); } finally { // Always stop log capture, even on error } var logs = captureLogs ? StopLogCapture() : new List(); return Response.Success( "C# script executed successfully.", new { result = result?.ToString(), logs = logs, log_count = logs.Count } ); } catch (Exception e) { var logs = captureLogs ? StopLogCapture() : new List(); Debug.LogError($"[ExecuteCSharpScript] Failed to execute script: {e}"); return Response.Error( $"C# script execution failed: {e.Message}", new { logs = logs, exception = e.ToString() } ); } } /// /// Internal method to execute the script using Roslyn. /// private static object ExecuteScriptInternal(string script, string[] imports) { try { // Collect assembly references var references = new List { typeof(UnityEngine.Debug).Assembly, typeof(UnityEditor.EditorApplication).Assembly }; // Add Assembly-CSharp if it exists (runtime user code) var assemblyCSharp = System.AppDomain.CurrentDomain.GetAssemblies() .FirstOrDefault(a => a.GetName().Name == "Assembly-CSharp"); if (assemblyCSharp != null) { references.Add(assemblyCSharp); } // Add Assembly-CSharp-Editor if it exists (editor user code) var assemblyCSharpEditor = System.AppDomain.CurrentDomain.GetAssemblies() .FirstOrDefault(a => a.GetName().Name == "Assembly-CSharp-Editor"); if (assemblyCSharpEditor != null) { references.Add(assemblyCSharpEditor); } // Add Unity.InputSystem if it exists (Input System package) var inputSystemAssembly = System.AppDomain.CurrentDomain.GetAssemblies() .FirstOrDefault(a => a.GetName().Name == "Unity.InputSystem"); if (inputSystemAssembly != null) { references.Add(inputSystemAssembly); } // Create script options with imports var options = ScriptOptions.Default .WithReferences(references) .WithImports(imports); // Execute the script synchronously var scriptTask = CSharpScript.EvaluateAsync(script, options); // Wait for the task to complete scriptTask.Wait(); return scriptTask.Result; } catch (AggregateException ae) { // Unwrap AggregateException to get the actual exception if (ae.InnerException != null) { throw ae.InnerException; } throw; } } /// /// Starts capturing Unity logs. /// private static void StartLogCapture(bool enabled) { if (!enabled) { _isCapturingLogs = false; return; } _capturedLogs.Clear(); _isCapturingLogs = true; Application.logMessageReceived += OnLogMessageReceived; } /// /// Stops capturing logs and returns the captured log list. /// private static List StopLogCapture() { Application.logMessageReceived -= OnLogMessageReceived; _isCapturingLogs = false; var logs = new List(_capturedLogs); _capturedLogs.Clear(); return logs; } /// /// Log message callback handler. /// private static void OnLogMessageReceived(string logString, string stackTrace, LogType type) { if (!_isCapturingLogs) return; var logEntry = new StringBuilder(); logEntry.Append($"[{type}] {logString}"); // Include stack trace for errors and exceptions if ((type == LogType.Error || type == LogType.Exception) && !string.IsNullOrEmpty(stackTrace)) { logEntry.Append($"\n{stackTrace}"); } _capturedLogs.Add(logEntry.ToString()); } } }