rabidus-test/Assets/Plugins/QFSW/Quantum Console/Source/Extras/exec/DynamicCodeCommands.cs

111 lines
5.5 KiB
C#
Raw Permalink Normal View History

2023-08-22 15:41:12 +03:00
#if NET_4_6 && !NET_STANDARD_2_0
#define QC_SUPPORTED
#endif
#if QC_SUPPORTED
using System;
using System.CodeDom.Compiler;
using System.IO;
using System.Reflection;
using System.Threading.Tasks;
namespace QFSW.QC
{
public static class DynamicCodeCommands
{
private const Platform execAvailability = Platform.AllPlatforms ^ (Platform.WebGLPlayer | Platform.IPhonePlayer | Platform.XboxOne | Platform.PS4 | Platform.Switch);
[CommandDescription("Loads the code at the specified file and compiles it to C# which will then be executed. Use with caution as no safety checks will be performed. Not supported in AOT (IL2CPP) builds." +
"\n\nBy default, boiler plate code will NOT be inserted around the code you provide. Please see 'exec' for more information about boilerplate insertion")]
[Command("exec-extern", execAvailability)]
private static async Task ExecuteExternalArbitaryCodeAsync(string filePath, bool insertBoilerplate = false)
{
if (!File.Exists(filePath)) { throw new ArgumentException($"file at the specified path '{filePath}' did not exist."); }
string code = File.ReadAllText(filePath);
await ExecuteArbitaryCodeAsync(code.Replace("”", "\"").Replace("“", "\""), insertBoilerplate);
}
[CommandDescription("Compiles the given code to C# which will then be executed. Use with caution as no safety checks will be performed. Not supported in AOT (IL2CPP) builds." +
"\n\nBy default, boiler plate code will be inserted around the code you provide. This means various namespaces will be included, and the main class and main function entry point will " +
"provided. In this case, the code you provide should be code that would exist within the body of the main function, and thus cannot contain things such as class definition. If you " +
"disable boiler plate insertion, you can write whatever code you want, however you must provide a static entry point called Main in a static class called Program")]
[Command("exec", execAvailability)]
private static async Task ExecuteArbitaryCodeAsync(string code, bool insertBoilerplate = true)
{
#if !UNITY_EDITOR && ENABLE_IL2CPP
await Task.FromException(new Exception("exec is not supported on AOT platforms such as IL2CPP and requires JIT (Mono)."));
#else
MethodInfo entryPoint = await Task.Run(() =>
{
string fullCode = string.Empty;
if (insertBoilerplate)
{
string[] includedNamespaces = new string[] { "System", "System.Collections", "System.Collections.Generic",
"System.Reflection", "System.Linq", "System.Text", "System.Globalization",
"UnityEngine", "UnityEngine.Events", "UnityEngine.EventSystems", "UnityEngine.UI" };
for (int i = 0; i < includedNamespaces.Length; i++) { fullCode += $"using {includedNamespaces[i]};\n"; }
fullCode += @"
public class Program
{
public static void Main()
{"
+ code +
@"}
}";
}
else { fullCode = code; }
Assembly assembly = CompileCode(fullCode);
BindingFlags searchFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static;
Type program = assembly.GetType("Program");
if (program == null) { throw new ArgumentException("Code Execution Failure - required static class Program could not be found"); }
entryPoint = program.GetMethod("Main", searchFlags);
if (entryPoint == null) { throw new ArgumentException("Code Execution Failure - required static entry point Main could not be found"); }
return entryPoint;
});
entryPoint.Invoke(null, null);
#endif
}
private static Assembly CompileCode(string code)
{
#if !UNITY_EDITOR && ENABLE_IL2CPP
throw new Exception("Code compilation is not supported on AOT platforms such as IL2CPP and requires JIT (Mono).");
#else
CSharpCompiler.CodeCompiler compiler = new CSharpCompiler.CodeCompiler();
CompilerParameters compilerParams = new CompilerParameters();
Assembly[] allLoadedAssemblies = AppDomain.CurrentDomain.GetAssemblies();
compilerParams.GenerateExecutable = false;
compilerParams.GenerateInMemory = true;
for (int i = 0; i < allLoadedAssemblies.Length; i++)
{
if (!allLoadedAssemblies[i].IsDynamic)
{
string dllName = allLoadedAssemblies[i].Location;
compilerParams.ReferencedAssemblies.Add(dllName);
}
}
CompilerResults compiledCode = compiler.CompileAssemblyFromSource(compilerParams, code);
if (compiledCode.Errors.HasErrors)
{
string errorMessage = "Code Compilation Failure";
for (int i = 0; i < compiledCode.Errors.Count; i++)
{
errorMessage += $"\n{compiledCode.Errors[i].ErrorNumber} - {compiledCode.Errors[i].ErrorText}";
}
throw new ArgumentException(errorMessage);
}
return compiledCode.CompiledAssembly;
#endif
}
}
}
#endif