Init
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
#if NET_4_6 && !NET_STANDARD_2_0
|
||||
#define QC_SUPPORTED
|
||||
#endif
|
||||
|
||||
#if QC_SUPPORTED && !QC_DISABLED && !QC_DISABLE_BUILTIN_ALL && !QC_DISABLE_BUILTIN_EXTRA
|
||||
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
|
||||
Reference in New Issue
Block a user