diff --git a/GamecraftScripting/Commands/DebugCommandEngine.cs b/GamecraftScripting/Commands/DebugCommandEngine.cs index e5e48a4..96274ba 100644 --- a/GamecraftScripting/Commands/DebugCommandEngine.cs +++ b/GamecraftScripting/Commands/DebugCommandEngine.cs @@ -14,15 +14,15 @@ namespace GamecraftScripting.Commands { class DebugCommandEngine : ICustomCommandEngine { - public string Description { get; } = ""; + public string Description { get; } = "Display Python debug info"; - public string Name { get; } = ""; + public string Name { get; } = "PythonInfo"; public IEntitiesDB entitiesDB { set; private get; } public void Dispose() { - CommandRegistrationHelper.Unregister("IronPythonInfo"); + CommandRegistrationHelper.Unregister("PythonInfo"); } public void Ready() @@ -32,7 +32,7 @@ namespace GamecraftScripting.Commands private void ironPythonInfo() { - Logging.CommandLog(typeof(PythonOptions).Assembly.GetName().ToString()); + Logging.CommandLog($"Assembly {typeof(PythonOptions).Assembly.GetName().ToString()}"); } } } diff --git a/GamecraftScripting/Commands/PythonRunnerCommandEngine.cs b/GamecraftScripting/Commands/PythonRunnerCommandEngine.cs index 8e34269..ae04810 100644 --- a/GamecraftScripting/Commands/PythonRunnerCommandEngine.cs +++ b/GamecraftScripting/Commands/PythonRunnerCommandEngine.cs @@ -5,14 +5,18 @@ using System.Numerics; using System.Linq; using System.Text; using System.Threading.Tasks; +using System.Security.Policy; using GamecraftModdingAPI.Commands; using GamecraftModdingAPI.Utility; using Svelto.ECS; +using RobocraftX.Common; using IronPython.Hosting; using Microsoft.Scripting.Hosting; +using GamecraftScripting.Environment; + namespace GamecraftScripting.Commands { class PythonRunnerCommandEngine : ICustomCommandEngine @@ -23,25 +27,85 @@ namespace GamecraftScripting.Commands public IEntitiesDB entitiesDB { set; private get; } - private ScriptEngine pythonEngine; + private ScriptEngine pyEngine; private ScriptScope gameScope; public void Dispose() { CommandRegistrationHelper.Unregister("RunPythonScript"); + CommandRegistrationHelper.Unregister("RunPython"); } public void Ready() { CommandRegistrationHelper.Register("RunPythonScript", RunPythonScript, "Run a python script stored on your computer"); - this.pythonEngine = Python.CreateEngine(); - this.gameScope = pythonEngine.CreateScope(); + CommandRegistrationHelper.Register("RunPython", RunPythonString, "Run a python argument"); + // create insecure Python namespace + CreateFreeInstance(); + // make "import GamecraftModdingAPI" work without the clr boilerplate + pyEngine.Execute("import clr\nclr.AddReference(\"GamecraftModdingAPI\")", gameScope); + ICollection searchPaths = pyEngine.GetSearchPaths(); + searchPaths.Add(@".\Lib\"); + searchPaths.Add(@".\Gamecraft_Data\Managed\Lib\"); + pyEngine.SetSearchPaths(searchPaths); + Logging.MetaLog(string.Join(", ", pyEngine.GetSearchPaths().ToArray())); } private void RunPythonScript(string scriptPath) { - pythonEngine.ExecuteFile(scriptPath, gameScope); + string script = File.ReadAllText(scriptPath); + ExecuteScript(script); + } + + private void RunPythonString(string expression) + { + ExecuteScript(expression); + } + + private void ExecuteScript(string script, bool logError=true) + { + ScriptSource src = pyEngine.CreateScriptSourceFromString(script); + try + { + src.Execute(gameScope); + } + catch (Exception e) + { + if (logError) + { + Logging.CommandLogError($"Python error: {e.Message}"); + } + else + { + throw e; + } + + } + } + + /// + /// [Experimental] Create a Python instance without access to any(?) assemblies + /// + private void CreateSandboxedInstance() + { + Evidence evidence = new Evidence(); // an uninformative class name to limit accessible Assemblies + AppDomain domain = AppDomain.CreateDomain(GameMode.SaveGameDetails.Name, evidence); + this.pyEngine = Python.CreateEngine(domain); + PythonLogStream pyLog = new PythonLogStream(); + pyEngine.Runtime.IO.SetOutput(pyLog, pyLog.Encoding); + this.gameScope = pyEngine.CreateScope(); + } + + /// + /// Create an unsecured Python instance + /// + private void CreateFreeInstance() + { + this.pyEngine = Python.CreateEngine(); + PythonLogStream pyLog = new PythonLogStream(); + pyEngine.Runtime.IO.SetOutput(pyLog, pyLog.Encoding); + this.gameScope = pyEngine.CreateScope(); } public PythonRunnerCommandEngine() diff --git a/GamecraftScripting/Environment/LogStream.cs b/GamecraftScripting/Environment/LogStream.cs new file mode 100644 index 0000000..6973d59 --- /dev/null +++ b/GamecraftScripting/Environment/LogStream.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using GamecraftModdingAPI.Utility; + +namespace GamecraftScripting.Environment +{ + /// + /// Writeable Stream for Python's print function + /// + class PythonLogStream : Stream + { + public Encoding Encoding { get; set; } = Encoding.Default; + + public override bool CanRead => false; + + public override bool CanSeek => false; + + public override bool CanWrite => true; + + public override long Length => _length; + + private long _length = 0L; + + private StringBuilder strBuffer = new StringBuilder(); + + public override long Position { get => _length-1; set => throw new NotImplementedException(); } + + public override void Flush() + { + if (strBuffer.Length > 0) + { + Logging.CommandLog(strBuffer.ToString().Trim()); + strBuffer.Clear(); + } + } + + public override int Read(byte[] buffer, int offset, int count) + { + throw new NotImplementedException(); + } + + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotImplementedException(); + } + + public override void SetLength(long value) + { + throw new NotImplementedException(); + } + + public override void Write(byte[] buffer, int offset, int count) + { + byte[] realBuffer = new byte[count + offset]; + for (int i = 0; i < realBuffer.Length-offset; i++) + { + realBuffer[i + offset] = buffer[i]; + } + string toWrite = this.Encoding.GetString(realBuffer); + for (int i = 0; i < toWrite.Length; i++) + { + WriteChar(toWrite[i]); + } + } + + public void WriteChar(char c) + { + if (c == '\n') + { + Flush(); + } + else if (c == '\r') + { + // Microsoft motto: Why use 1 when 2 will clearly be more bloated! + } + else + { + strBuffer.Append(c); + } + } + } +}