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());
}
}
}