111 lines
5.5 KiB
C#
111 lines
5.5 KiB
C#
|
#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
|