From 2f96e221a3b37081c5eadb2facf70169c7c74da0 Mon Sep 17 00:00:00 2001 From: Paul Merrill Date: Thu, 4 Apr 2013 17:42:16 -0700 Subject: [PATCH] FIX: python import This patch is actually rather large and represents a significant amount of hacking. Here is an incomplete list of changes: - support for Python local variables (large accomplishment :-) - proper handling of Python imports (also large) - addition of Bytecode class to wrap around PyCodeObject and contain a private local variables - usage of __init__.py in various places, instead of init.py --- data/base/__init__.py | 2 + data/base/baseapi/{init.py => __init__.py} | 0 data/base/init.py | 1 + src/Makefile | 150 ++++++++-------- src/Makefile.common | 1 + src/area-tmx.cpp | 36 ++-- src/area.cpp | 12 +- src/area.h | 2 +- src/bytecode.cpp | 121 +++++++++++++ src/{scriptinst.h => bytecode.h} | 79 +++++---- src/entity.cpp | 26 ++- src/entity.h | 2 +- src/music.cpp | 2 +- src/python.cpp | 265 +++++++++++------------------ src/python.h | 44 +++-- src/pyworldfinder.cpp | 165 ++++++++++++++++++ src/{scriptinst.h => pyworldfinder.h} | 62 ++----- src/reader.cpp | 59 +++++-- src/reader.h | 7 +- src/scriptinst.cpp | 132 +++++--------- src/scriptinst.h | 22 +-- src/tile.cpp | 9 +- src/tile.h | 2 +- src/world.cpp | 20 ++- src/world.h | 6 +- 25 files changed, 715 insertions(+), 512 deletions(-) create mode 100644 data/base/__init__.py rename data/base/baseapi/{init.py => __init__.py} (100%) create mode 100644 src/bytecode.cpp copy src/{scriptinst.h => bytecode.h} (55%) create mode 100644 src/pyworldfinder.cpp copy src/{scriptinst.h => pyworldfinder.h} (54%) diff --git a/data/base/__init__.py b/data/base/__init__.py new file mode 100644 index 0000000..01e7de8 --- /dev/null +++ b/data/base/__init__.py @@ -0,0 +1,2 @@ +import baseapi + diff --git a/data/base/baseapi/init.py b/data/base/baseapi/__init__.py similarity index 100% rename from data/base/baseapi/init.py rename to data/base/baseapi/__init__.py diff --git a/data/base/init.py b/data/base/init.py index ec2cda3..6416acc 100644 --- a/data/base/init.py +++ b/data/base/init.py @@ -1,2 +1,3 @@ +import baseapi import baseapi.init diff --git a/src/Makefile b/src/Makefile index 6d4d379..cd16daa 100644 --- a/src/Makefile +++ b/src/Makefile @@ -7,12 +7,12 @@ include Makefile.common -OBJECTS = animation.o area.o area-tmx.o bitrecord.o character.o client-conf.o \ - gosu-cbuffer.o image.o image-impl.o nbcl/nbcl.o entity.o log.o \ - main.o music.o npc.o overlay.o player.o python.o python-bindings.o \ - random.o reader.o scriptinst.o sound.o string.o tile.o \ - tiledimage.o tiledimage-impl.o timer.o timeout.o vec.o viewport.o \ - window.o world.o xml.o +OBJECTS = animation.o area.o area-tmx.o bitrecord.o bytecode.o character.o \ + client-conf.o gosu-cbuffer.o image.o image-impl.o nbcl/nbcl.o \ + entity.o log.o main.o music.o npc.o overlay.o player.o python.o \ + python-bindings.o pyworldfinder.o random.o reader.o scriptinst.o \ + sound.o string.o tile.o tiledimage.o tiledimage-impl.o timer.o \ + timeout.o vec.o viewport.o window.o world.o xml.o # Name of testing world. BASEDATA = ../data/base.zip @@ -64,32 +64,34 @@ clean: ### --- DO NOT DELETE THIS LINE --- ### #pragma GCC pch_preprocess "./precompile.h.gch" -animation.o: animation.cpp animation.h cache.h client-conf.h image.h log.h \ - reader.h sound.h tiledimage.h vec.h window.h xml.h +animation.o: animation.cpp animation.h bytecode.h cache.h client-conf.h \ + image.h log.h reader.h sound.h tiledimage.h vec.h window.h xml.h #pragma GCC pch_preprocess "./precompile.h.gch" -area-tmx.o: animation.h area-tmx.cpp area-tmx.h area.h bitrecord.h cache.h \ - character.h client-conf.h entity.h image.h log.h player.h python.h reader.h \ - scriptinst.h sound.h string.h tile.h tiledimage.h vec.h viewport.h window.h \ - world.h xml.h +area-tmx.o: animation.h area-tmx.cpp area-tmx.h area.h bitrecord.h \ + bytecode.h cache.h character.h client-conf.h entity.h image.h log.h \ + player.h python.h reader.h scriptinst.h sound.h string.h tile.h \ + tiledimage.h vec.h viewport.h window.h world.h xml.h #pragma GCC pch_preprocess "./precompile.h.gch" -area.o: animation.h area.cpp area.h bitrecord.h cache.h character.h \ - client-conf.h entity.h image.h log.h music.h npc.h overlay.h player.h \ - python.h reader.h scriptinst.h sound.h tile.h tiledimage.h vec.h viewport.h \ - window.h world.h xml.h +area.o: animation.h area.cpp area.h bitrecord.h bytecode.h cache.h \ + character.h client-conf.h entity.h image.h log.h music.h npc.h overlay.h \ + player.h python.h reader.h scriptinst.h sound.h tile.h tiledimage.h vec.h \ + viewport.h window.h world.h xml.h #pragma GCC pch_preprocess "./precompile.h.gch" bitrecord.o: bitrecord.cpp bitrecord.h window.h #pragma GCC pch_preprocess "./precompile.h.gch" -character.o: animation.h area.h cache.h character.cpp character.h \ +bytecode.o: bytecode.cpp bytecode.h log.h python.h +#pragma GCC pch_preprocess "./precompile.h.gch" +character.o: animation.h area.h bytecode.h cache.h character.cpp character.h \ client-conf.h entity.h image.h log.h python.h reader.h scriptinst.h sound.h \ tile.h tiledimage.h vec.h window.h xml.h #pragma GCC pch_preprocess "./precompile.h.gch" client-conf.o: client-conf.cpp client-conf.h log.h string.h vec.h \ nbcl/nbcl.h #pragma GCC pch_preprocess "./precompile.h.gch" -entity.o: animation.h area.h bitrecord.h cache.h character.h client-conf.h \ - entity.cpp entity.h image.h log.h player.h python.h reader.h scriptinst.h \ - sound.h string.h tile.h tiledimage.h vec.h viewport.h window.h world.h \ - xml.h +entity.o: animation.h area.h bitrecord.h bytecode.h cache.h character.h \ + client-conf.h entity.cpp entity.h image.h log.h player.h python.h reader.h \ + scriptinst.h sound.h string.h tile.h tiledimage.h vec.h viewport.h window.h \ + world.h xml.h #pragma GCC pch_preprocess "./precompile.h.gch" gosu-cbuffer.o: gosu-cbuffer.cpp gosu-cbuffer.h #pragma GCC pch_preprocess "./precompile.h.gch" @@ -97,83 +99,91 @@ image-impl.o: gosu-cbuffer.h image-impl.cpp image-impl.h image.h window.h #pragma GCC pch_preprocess "./precompile.h.gch" image.o: image.cpp image.h #pragma GCC pch_preprocess "./precompile.h.gch" -log.o: animation.h bitrecord.h cache.h character.h client-conf.h entity.h \ - image.h log.cpp log.h player.h python.h reader.h scriptinst.h sound.h \ - tile.h tiledimage.h vec.h viewport.h window.h world.h xml.h +log.o: animation.h bitrecord.h bytecode.h cache.h character.h client-conf.h \ + entity.h image.h log.cpp log.h player.h python.h reader.h scriptinst.h \ + sound.h tile.h tiledimage.h vec.h viewport.h window.h world.h xml.h #pragma GCC pch_preprocess "./precompile.h.gch" -main.o: cache.h client-conf.h image.h log.h main.cpp python.h reader.h \ - sound.h tiledimage.h vec.h window.h xml.h +main.o: bytecode.h cache.h client-conf.h image.h log.h main.cpp python.h \ + reader.h sound.h tiledimage.h vec.h window.h xml.h #pragma GCC pch_preprocess "./precompile.h.gch" -music.o: cache.h client-conf.h image.h log.h music.cpp music.h python.h \ - reader.h readercache.h sound.h tiledimage.h vec.h window.h xml.h +music.o: bytecode.h cache.h client-conf.h image.h log.h music.cpp music.h \ + python.h reader.h readercache.h sound.h tiledimage.h vec.h window.h xml.h #pragma GCC pch_preprocess "./precompile.h.gch" -npc.o: animation.h area.h cache.h character.h client-conf.h entity.h image.h \ - log.h npc.cpp npc.h python.h reader.h scriptinst.h sound.h tile.h \ - tiledimage.h vec.h window.h xml.h +npc.o: animation.h area.h bytecode.h cache.h character.h client-conf.h \ + entity.h image.h log.h npc.cpp npc.h python.h reader.h scriptinst.h sound.h \ + tile.h tiledimage.h vec.h window.h xml.h #pragma GCC pch_preprocess "./precompile.h.gch" os-windows.o: os-windows.cpp #pragma GCC pch_preprocess "./precompile.h.gch" -overlay.o: animation.h area.h cache.h client-conf.h entity.h image.h log.h \ - overlay.cpp overlay.h python.h reader.h scriptinst.h sound.h tile.h \ - tiledimage.h vec.h window.h xml.h +overlay.o: animation.h area.h bytecode.h cache.h client-conf.h entity.h \ + image.h log.h overlay.cpp overlay.h python.h reader.h scriptinst.h sound.h \ + tile.h tiledimage.h vec.h window.h xml.h #pragma GCC pch_preprocess "./precompile.h.gch" -player.o: animation.h area.h bitrecord.h cache.h character.h client-conf.h \ - entity.h image.h log.h player.cpp player.h python.h reader.h scriptinst.h \ - sound.h tile.h tiledimage.h vec.h viewport.h window.h world.h xml.h +player.o: animation.h area.h bitrecord.h bytecode.h cache.h character.h \ + client-conf.h entity.h image.h log.h player.cpp player.h python.h reader.h \ + scriptinst.h sound.h tile.h tiledimage.h vec.h viewport.h window.h world.h \ + xml.h #pragma GCC pch_preprocess "./precompile.h.gch" -python-bindings.o: animation.h area.h bitrecord.h cache.h character.h \ - client-conf.h entity.h image.h log.h music.h player.h python-bindings.cpp \ - python.h random.h reader.h scriptinst.h sound.h tile.h tiledimage.h \ - timeout.h timer.h vec.h viewport.h window.h world.h xml.h +python-bindings.o: animation.h area.h bitrecord.h bytecode.h cache.h \ + character.h client-conf.h entity.h image.h log.h music.h player.h \ + python-bindings.cpp python.h random.h reader.h scriptinst.h sound.h tile.h \ + tiledimage.h timeout.h timer.h vec.h viewport.h window.h world.h xml.h #pragma GCC pch_preprocess "./precompile.h.gch" -python.o: cache.h client-conf.h image.h log.h python-bindings.h python.cpp \ - python.h reader.h sound.h tiledimage.h vec.h window.h xml.h +python.o: bytecode.h cache.h client-conf.h image.h log.h python-bindings.h \ + python.cpp python.h pyworldfinder.h reader.h sound.h tiledimage.h vec.h \ + window.h xml.h +#pragma GCC pch_preprocess "./precompile.h.gch" +pyworldfinder.o: bytecode.h cache.h client-conf.h image.h log.h \ + pyworldfinder.cpp reader.h sound.h tiledimage.h vec.h window.h xml.h #pragma GCC pch_preprocess "./precompile.h.gch" random.o: python.h random.cpp random.h #pragma GCC pch_preprocess "./precompile.h.gch" -reader.o: cache.h client-conf.h image.h log.h python.h reader.cpp reader.h \ - sound.h tiledimage.h vec.h window.h xml.h +reader.o: bytecode.h cache.h client-conf.h image.h log.h python.h reader.cpp \ + reader.h sound.h tiledimage.h vec.h window.h xml.h #pragma GCC pch_preprocess "./precompile.h.gch" -scriptinst.o: cache.h client-conf.h image.h log.h python.h reader.h \ - scriptinst.cpp scriptinst.h sound.h tiledimage.h vec.h window.h xml.h +scriptinst.o: bytecode.h cache.h client-conf.h image.h log.h python.h \ + reader.h scriptinst.cpp scriptinst.h sound.h tiledimage.h vec.h window.h \ + xml.h #pragma GCC pch_preprocess "./precompile.h.gch" -sound.o: cache.h client-conf.h image.h log.h python.h reader.h sound.cpp \ - sound.h tiledimage.h vec.h window.h xml.h +sound.o: bytecode.h cache.h client-conf.h image.h log.h python.h reader.h \ + sound.cpp sound.h tiledimage.h vec.h window.h xml.h #pragma GCC pch_preprocess "./precompile.h.gch" string.o: log.h string.cpp string.h #pragma GCC pch_preprocess "./precompile.h.gch" -tile.o: animation.h area.h bitrecord.h cache.h character.h client-conf.h \ - entity.h image.h log.h player.h python-optional.h python.h reader.h \ - scriptinst.h sound.h string.h tile.cpp tile.h tiledimage.h vec.h viewport.h \ - window.h world.h xml.h +tile.o: animation.h area.h bitrecord.h bytecode.h cache.h character.h \ + client-conf.h entity.h image.h log.h player.h python-optional.h python.h \ + reader.h scriptinst.h sound.h string.h tile.cpp tile.h tiledimage.h vec.h \ + viewport.h window.h world.h xml.h #pragma GCC pch_preprocess "./precompile.h.gch" tiledimage-impl.o: gosu-cbuffer.h image-impl.h image.h tiledimage-impl.cpp \ tiledimage-impl.h tiledimage.h window.h #pragma GCC pch_preprocess "./precompile.h.gch" tiledimage.o: image.h tiledimage.cpp tiledimage.h #pragma GCC pch_preprocess "./precompile.h.gch" -timeout.o: animation.h bitrecord.h cache.h character.h client-conf.h \ - entity.h image.h log.h player.h python.h reader.h scriptinst.h sound.h \ - tile.h tiledimage.h timeout.cpp timeout.h vec.h viewport.h window.h world.h \ - xml.h +timeout.o: animation.h bitrecord.h bytecode.h cache.h character.h \ + client-conf.h entity.h image.h log.h player.h python.h reader.h \ + scriptinst.h sound.h tile.h tiledimage.h timeout.cpp timeout.h vec.h \ + viewport.h window.h world.h xml.h #pragma GCC pch_preprocess "./precompile.h.gch" -timer.o: animation.h bitrecord.h cache.h character.h client-conf.h entity.h \ - image.h log.h player.h python.h reader.h scriptinst.h sound.h tile.h \ - tiledimage.h timer.cpp timer.h vec.h viewport.h window.h world.h xml.h +timer.o: animation.h bitrecord.h bytecode.h cache.h character.h \ + client-conf.h entity.h image.h log.h player.h python.h reader.h \ + scriptinst.h sound.h tile.h tiledimage.h timer.cpp timer.h vec.h viewport.h \ + window.h world.h xml.h #pragma GCC pch_preprocess "./precompile.h.gch" vec.o: vec.cpp vec.h #pragma GCC pch_preprocess "./precompile.h.gch" -viewport.o: animation.h area.h cache.h client-conf.h entity.h image.h log.h \ - python.h reader.h scriptinst.h sound.h tile.h tiledimage.h vec.h \ - viewport.cpp viewport.h window.h xml.h +viewport.o: animation.h area.h bytecode.h cache.h client-conf.h entity.h \ + image.h log.h python.h reader.h scriptinst.h sound.h tile.h tiledimage.h \ + vec.h viewport.cpp viewport.h window.h xml.h #pragma GCC pch_preprocess "./precompile.h.gch" -window.o: animation.h bitrecord.h cache.h character.h client-conf.h entity.h \ - image.h log.h player.h python.h reader.h scriptinst.h sound.h tile.h \ - tiledimage.h vec.h viewport.h window.cpp window.h world.h xml.h +window.o: animation.h bitrecord.h bytecode.h cache.h character.h \ + client-conf.h entity.h image.h log.h player.h python.h reader.h \ + scriptinst.h sound.h tile.h tiledimage.h vec.h viewport.h window.cpp \ + window.h world.h xml.h #pragma GCC pch_preprocess "./precompile.h.gch" -world.o: animation.h area-tmx.h area.h bitrecord.h cache.h character.h \ - client-conf.h entity.h image.h log.h music.h player.h python.h reader.h \ - scriptinst.h sound.h tile.h tiledimage.h timeout.h vec.h viewport.h \ - window.h world.cpp world.h xml.h +world.o: animation.h area-tmx.h area.h bitrecord.h bytecode.h cache.h \ + character.h client-conf.h entity.h image.h log.h music.h player.h python.h \ + reader.h scriptinst.h sound.h tile.h tiledimage.h timeout.h vec.h \ + viewport.h window.h world.cpp world.h xml.h #pragma GCC pch_preprocess "./precompile.h.gch" xml.o: log.h string.h xml.cpp xml.h diff --git a/src/Makefile.common b/src/Makefile.common index 6ad72e4..13f4e89 100644 --- a/src/Makefile.common +++ b/src/Makefile.common @@ -1,6 +1,7 @@ # Warning flags to enable. WFLAGS += -Wall -Wextra -Wconversion -Wdeprecated -Winvalid-pch WFLAGS += -Wno-long-long # Disable warnings in Python code. +WFLAGS += -Wno-missing-field-initializers # Nice when dealing with Python structs # Compiler and linker flags. CXXFLAGS += -DUSE_PRECOMPILED_HEADERS $(BLDCFLAGS) -pipe -pedantic $(WFLAGS) \ diff --git a/src/area-tmx.cpp b/src/area-tmx.cpp index db64b0e..6073286 100644 --- a/src/area-tmx.cpp +++ b/src/area-tmx.cpp @@ -157,30 +157,30 @@ bool AreaTMX::processMapProperties(XMLNode node) else if (name == "on_load") { std::string filename = value; ScriptInst script(filename); - if (!script.validate(descriptor)) + if (!script.validate()) return false; - loadScript = filename; + loadScript = script; } else if (name == "on_focus") { std::string filename = value; ScriptInst script(filename); - if (!script.validate(descriptor)) + if (!script.validate()) return false; - focusScript = filename; + focusScript = script; } else if (name == "on_tick") { std::string filename = value; ScriptInst script(filename); - if (!script.validate(descriptor)) + if (!script.validate()) return false; - tickScript = filename; + tickScript = script; } else if (name == "on_turn") { std::string filename = value; ScriptInst script(filename); - if (!script.validate(descriptor)) + if (!script.validate()) return false; - turnScript = filename; + turnScript = script; } else if (name == "loop") { loopX = value.find('x') != std::string::npos; @@ -341,23 +341,23 @@ bool AreaTMX::processTileType(XMLNode node, TileType& type, else if (name == "on_enter") { std::string filename = value; ScriptInst script(filename); - if (!script.validate(descriptor)) + if (!script.validate()) return false; - type.enterScript = filename; + type.enterScript = script; } else if (name == "on_leave") { std::string filename = value; ScriptInst script(filename); - if (!script.validate(descriptor)) + if (!script.validate()) return false; - type.leaveScript = filename; + type.leaveScript = script; } else if (name == "on_use") { std::string filename = value; ScriptInst script(filename); - if (!script.validate(descriptor)) + if (!script.validate()) return false; - type.useScript = filename; + type.useScript = script; } else if (name == "frames") { std::string memtemp; @@ -632,7 +632,7 @@ bool AreaTMX::processObject(XMLNode node, int z) // Gather object properties now. Assign them to tiles later. bool wwide[5], hwide[5]; /* wide exit in dimensions: width, height */ - ScriptInst enterScript, leaveScript, useScript; + boost::optional enterScript, leaveScript, useScript; boost::scoped_ptr exit[5]; boost::optional layermods[5]; unsigned flags = 0x0; @@ -652,21 +652,21 @@ bool AreaTMX::processObject(XMLNode node, int z) else if (name == "on_enter") { std::string filename = value; ScriptInst script(filename); - if (!script.validate(descriptor)) + if (!script.validate()) return false; enterScript = script; } else if (name == "on_leave") { std::string filename = value; ScriptInst script(filename); - if (!script.validate(descriptor)) + if (!script.validate()) return false; leaveScript = script; } else if (name == "on_use") { std::string filename = value; ScriptInst script(filename); - if (!script.validate(descriptor)) + if (!script.validate()) return false; useScript = script; } diff --git a/src/area.cpp b/src/area.cpp index 919b4f7..acc73f5 100644 --- a/src/area.cpp +++ b/src/area.cpp @@ -105,7 +105,8 @@ void Area::focus() Music::setLoop(musicLoop); pythonSetGlobal("Area", this); - focusScript.invoke(); + if (focusScript) + focusScript->invoke(); } void Area::buttonDown(const Gosu::Button btn) @@ -179,7 +180,8 @@ void Area::requestRedraw() void Area::tick(unsigned long dt) { pythonSetGlobal("Area", this); - tickScript.invoke(); + if (tickScript) + tickScript->invoke(); BOOST_FOREACH(Overlay* o, overlays) { pythonSetGlobal("Area", this); @@ -203,7 +205,8 @@ void Area::tick(unsigned long dt) void Area::turn() { pythonSetGlobal("Area", this); - turnScript.invoke(); + if (turnScript) + turnScript->invoke(); pythonSetGlobal("Area", this); player->turn(); @@ -546,7 +549,8 @@ void Area::runLoadScripts() world->runAreaLoadScript(this); pythonSetGlobal("Area", this); - loadScript.invoke(); + if (loadScript) + loadScript->invoke(); } void Area::drawTiles() diff --git a/src/area.h b/src/area.h index 45d6da9..654c051 100644 --- a/src/area.h +++ b/src/area.h @@ -176,7 +176,7 @@ public: // // Script hooks. - ScriptInst loadScript, focusScript, tickScript, turnScript; + boost::optional loadScript, focusScript, tickScript, turnScript; protected: diff --git a/src/bytecode.cpp b/src/bytecode.cpp new file mode 100644 index 0000000..01db0a7 --- /dev/null +++ b/src/bytecode.cpp @@ -0,0 +1,121 @@ +/********************************* +** Tsunagari Tile Engine ** +** python.cpp ** +** Copyright 2011-2012 OmegaSDG ** +*********************************/ + +// "OmegaSDG" is defined as Michael D. Reiley and Paul Merrill. + +// ********** +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// ********** + + +#ifdef __APPLE__ +#include +#endif + +#include +#include +#include + +#include "bytecode.h" +#include "log.h" +#include "python.h" + + +static PyCodeObject* compile(const std::string& fn, const std::string& code) +{ + PyArena *arena = PyArena_New(); + if (!arena) { + Log::err("Python", fn + ": failed to create arena"); + return NULL; + } + + mod_ty mod = PyParser_ASTFromString(code.c_str(), fn.c_str(), + Py_file_input, NULL, arena); + if (!mod) { + Log::err("Python", fn + ": failed to parse"); + pythonErr(); + return NULL; + } + + PyCodeObject* co = PyAST_Compile(mod, fn.c_str(), NULL, arena); + if (!co) { + Log::err("Python", fn + ": failed to compile"); + pythonErr(); + return NULL; + } + + PyArena_Free(arena); + return co; +} + + +static bool executeBytecode(PyCodeObject* code, PyObject* globals, PyObject* locals) +{ + assert(code && globals && locals); + + inPythonScript++; + PyObject* result = PyEval_EvalCode(code, globals, locals); + inPythonScript--; + + if (!result) + pythonErr(); + return result; +} + + +Bytecode::Bytecode(const std::string& filename, + const std::string& sourcecode) + : fn(filename), + code(compile(filename, sourcecode)), + locals(PyDict_New()) +{ +} + + +Bytecode::~Bytecode() +{ + Py_XDECREF(code); + Py_XDECREF(locals); +} + + +bool Bytecode::execute() +{ + assert(valid()); + + PyObject* globals = pythonGlobals(); + Log::err("foo", PyString_AsString(PyObject_Repr(globals))); + return executeBytecode(code, globals, locals); +} + + +bool Bytecode::valid() const +{ + return code && locals; +} + + +const std::string& Bytecode::filename() const +{ + return fn; +} + diff --git a/src/scriptinst.h b/src/bytecode.h similarity index 55% copy from src/scriptinst.h copy to src/bytecode.h index 9458820..e2bb3d6 100644 --- a/src/scriptinst.h +++ b/src/bytecode.h @@ -1,69 +1,68 @@ /********************************* ** Tsunagari Tile Engine ** -** scriptinst.h ** +** python-script.h ** ** Copyright 2011-2012 OmegaSDG ** *********************************/ // "OmegaSDG" is defined as Michael D. Reiley and Paul Merrill. // ********** -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to -// deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the Software is +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // -// The above copyright notice and this permission notice shall be included in +// The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. // ********** -#ifndef SCRIPTINST_H -#define SCRIPTINST_H +#ifndef PYTHON_SCRIPT_H +#define PYTHON_SCRIPT_H #include -#include -#include +#include -class ScriptInst +// from Python +#include +#include + +class Bytecode { public: - ScriptInst(); - ScriptInst(const std::string& strloc); - ScriptInst(boost::python::object pyfn); + //! Compile a Python script. Must provide both a representative + //! filename for any error messages along with a string containing the + //! body of code to compile. Prints any errors on failure. + Bytecode(const std::string& filename, const std::string& sourcecode); + ~Bytecode(); - // context = domain for errors msgs - bool validate(const std::string& context); - bool invoke(); + //! Run a compiled Python script. Returns false on runtime error and + //! prints the error. + bool execute(); -private: - struct strref { - std::string filename; - std::string funcname; - PyCodeObject* funccall; - }; + //! Returns true if this object was constructed correctly. + bool valid() const; - boost::variant< - void*, - strref, - boost::python::object - > data; + const std::string& filename() const; - friend struct validate_visitor; - friend struct invoke_visitor; - friend struct topython_visitor; - friend struct scriptinst_to_python; +private: + std::string fn; + + PyCodeObject* code; + PyObject* locals; }; -void exportScriptInst(); +typedef boost::shared_ptr BytecodeRef; #endif + diff --git a/src/entity.cpp b/src/entity.cpp index 297af27..ce28718 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -69,9 +69,11 @@ Entity::Entity() Entity::~Entity() { - pythonSetGlobal("Area", area); - pythonSetGlobal("Entity", this); - deleteScript.invoke(); + if (deleteScript) { + pythonSetGlobal("Area", area); + pythonSetGlobal("Entity", this); + deleteScript->invoke(); + } } bool Entity::init(const std::string& descriptor) @@ -577,34 +579,42 @@ void Entity::enterTile(Tile* t) void Entity::runTickScript() { + if (!tickScript) + return; pythonSetGlobal("Area", area); pythonSetGlobal("Entity", this); pythonSetGlobal("Tile", getTile()); - tickScript.invoke(); + tickScript->invoke(); } void Entity::runTurnScript() { + if (!turnScript) + return; pythonSetGlobal("Area", area); pythonSetGlobal("Entity", this); pythonSetGlobal("Tile", getTile()); - turnScript.invoke(); + turnScript->invoke(); } void Entity::runTileExitScript() { + if (!tileExitScript) + return; pythonSetGlobal("Area", area); pythonSetGlobal("Entity", this); pythonSetGlobal("Tile", getTile()); - tileExitScript.invoke(); + tileExitScript->invoke(); } void Entity::runTileEntryScript() { + if (!tileEntryScript) + return; pythonSetGlobal("Area", area); pythonSetGlobal("Entity", this); pythonSetGlobal("Tile", getTile()); - tileEntryScript.invoke(); + tileEntryScript->invoke(); } @@ -767,7 +777,7 @@ bool Entity::processScript(const XMLNode node) } ScriptInst script(filename); - if (!script.validate(descriptor)) + if (!script.validate()) return false; if (!setScript(trigger, script)) { diff --git a/src/entity.h b/src/entity.h index ae6fa7f..aaab32c 100644 --- a/src/entity.h +++ b/src/entity.h @@ -175,7 +175,7 @@ public: // //! Script hooks. - ScriptInst tickScript, turnScript, tileEntryScript, + boost::optional tickScript, turnScript, tileEntryScript, tileExitScript, deleteScript; diff --git a/src/music.cpp b/src/music.cpp index 0535539..0b2b056 100644 --- a/src/music.cpp +++ b/src/music.cpp @@ -60,7 +60,7 @@ static std::string curLoop; static SongRef genSong(const std::string& name) { - BufferPtr buffer(Reader::readBuffer(name)); + boost::scoped_ptr buffer(Reader::readBuffer(name)); if (!buffer) return SongRef(); return SongRef(new Gosu::Song(buffer->frontReader())); diff --git a/src/python.cpp b/src/python.cpp index f115862..1d6de6d 100644 --- a/src/python.cpp +++ b/src/python.cpp @@ -25,6 +25,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. // ********** + #ifdef __APPLE__ #include #endif @@ -39,6 +40,7 @@ #include "log.h" #include "python.h" #include "python-bindings.h" // for pythonInitBindings +#include "pyworldfinder.h" #include "reader.h" #include "window.h" @@ -47,114 +49,34 @@ namespace bp = boost::python; -static bp::object modMain, modBltin; -static bp::object dictMain, dictBltin; +static PyObject* mainModule; +static PyObject* mainDict; int inPythonScript = 0; -//! List of known safe Python modules allowed for importing. -static std::string moduleWhitelist[] = { - "__builtin__", - "__main__", - "math", - "traceback", - "", -}; - -static bool inWhitelist(const std::string& name) -{ - for (int i = 0; moduleWhitelist[i].size(); i++) - if (name == moduleWhitelist[i]) - return true; - return false; -} - - -static void pythonIncludeModule(const char* name) -{ - bp::object module(bp::handle<>(PyImport_ImportModule(name))); - dictMain[name] = module; -} - -static void pythonSetDefaultEncoding(const char* enc) -{ - if (PyUnicode_SetDefaultEncoding(enc) != 0) { - PyErr_Format(PyExc_SystemError, - "encoding %s not found", enc); - bp::throw_error_already_set(); - } -} - - -static PyObject* -safeImport(PyObject*, PyObject* args, PyObject* kwds) -{ - static const char* kwlist[] = {"name", "globals", "locals", "fromlist", - "level", 0}; - char* _name; - std::string name; - PyObject* globals, *locals, *fromList; - int level = -1; - - // Validate args from Python. - if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|OOOi:__import__", - (char**)kwlist, &_name, &globals, &locals, &fromList, - &level)) - return NULL; - name = _name; - - // Search whitelisted Python modules. - if (inWhitelist(name)) - return PyImport_ImportModuleLevel(_name, globals, locals, - fromList, level); - - Log::info("Python", "import " + name); - - // Search Python scripts inside World. - std::replace(name.begin(), name.end(), '.', '/'); - name += ".py"; - if (Reader::resourceExists(name)) { - if (Reader::runPythonScript(name)) - return modMain.ptr(); // We have to return a module... - else - // Error running imported script. Error is stored in - // Python. Return NULL here to 'rethrow' it. - return NULL; - } - - // Nothing acceptable found. - std::string msg = std::string("Module '") + _name + "' not found or " - "not allowed. Note that Tsunagari runs in a sandbox and does " - "not allow most external modules."; - PyErr_Format(PyExc_ImportError, "%s", msg.c_str()); - return NULL; -} static PyObject* nullExecfile(PyObject*, PyObject*) { PyErr_SetString(PyExc_RuntimeError, - "file(): Tsunagari runs scripts in a sandbox and " - "does not allow accessing the standard filesystem"); + "file(): Tsunagari does not allow accessing the standard filesystem"); return NULL; } static PyObject* nullFile(PyObject*, PyObject*) { PyErr_SetString(PyExc_RuntimeError, - "file(): Tsunagari runs scripts in a sandbox and " - "does not allow accessing the standard filesystem"); + "file(): Tsunagari does not allow accessing the standard filesystem"); return NULL; } static PyObject* nullOpen(PyObject*, PyObject*) { PyErr_SetString(PyExc_RuntimeError, - "open(): Tsunagari runs scripts in a sandbox and " - "does not allow accessing the standard filesystem"); + "open(): Tsunagari does not allow accessing the standard filesystem"); return NULL; } -static PyObject* nullPrint(PyObject*, PyObject*) +static PyObject* nullPrint(PyObject*, PyObject*, PyObject*) { PyErr_SetString(PyExc_RuntimeError, "print(): Tsunagari does not allow scripts to print"); @@ -169,51 +91,102 @@ static PyObject* nullReload(PyObject*, PyObject*) } PyMethodDef nullMethods[] = { - {"__import__", (PyCFunction)safeImport, METH_VARARGS | METH_KEYWORDS, ""}, - {"execfile", nullExecfile, METH_VARARGS, ""}, - {"file", nullFile, METH_VARARGS, ""}, - {"open", nullOpen, METH_VARARGS, ""}, - {"print", nullPrint, METH_VARARGS | METH_KEYWORDS, ""}, - {"reload", nullReload, METH_O, ""}, - {NULL, NULL, 0, NULL}, + {"execfile", (PyCFunction)nullExecfile, METH_VARARGS, ""}, + {"file", (PyCFunction)nullFile, METH_VARARGS, ""}, + {"open", (PyCFunction)nullOpen, METH_VARARGS, ""}, + {"print", (PyCFunction)nullPrint, METH_VARARGS | METH_KEYWORDS, ""}, + {"reload", (PyCFunction)nullReload, METH_O, ""}, + {NULL, NULL, 0, NULL} }; +static bool sysPathAppend(const std::string& path) +{ + PyObject* pypath = NULL; + PyObject* syspath = NULL; + int idx = -1; + + pypath = PyString_FromString(path.c_str()); + if (pypath == NULL) + goto err; + + syspath = PySys_GetObject((char*)"path"); + if (syspath == NULL || !PyList_Check(syspath)) { + Log::fatal("Python", + "sys.path must be a list of strings"); + goto err; + } + + idx = PyList_Append(syspath, pypath); + if (idx == -1) { + Log::fatal("Python", "failed to append to sys.path"); + goto err; + } + + Py_DECREF(pypath); + return true; + +err: + Py_XDECREF(pypath); + return false; +} + bool pythonInit() { - try { - PyImport_AppendInittab("tsunagari", &pythonInitBindings); - Py_Initialize(); - pythonSetDefaultEncoding("utf-8"); - - modMain = bp::import("__main__"); - dictMain = modMain.attr("__dict__"); - modBltin = bp::import("__builtin__"); - dictBltin = modBltin.attr("__dict__"); - - pythonIncludeModule("tsunagari"); - - // Hack in some rough safety. Disable external scripts and IO. - // InitModule doesn't remove existing modules, so we can use it to - // insert new methods into a pre-existing module. - PyObject* module = Py_InitModule("__builtin__", nullMethods); - if (module == NULL) - bp::throw_error_already_set(); - - // Restore the default SIGINT handler. - // Python messes with it. >:( - PyOS_setsig(SIGINT, SIG_DFL); - } catch (bp::error_already_set) { - Log::fatal("Python", "An error occured while populating the " - "Python modules:"); - Log::setVerbosity(V_NORMAL); // Assure message can be seen. - pythonErr(); - return false; + PyObject* name = NULL; + PyObject* tsuModule = NULL; + + PyImport_AppendInittab("tsunagari", &pythonInitBindings); + Py_InitializeEx(0); + + if (PyUnicode_SetDefaultEncoding("utf-8")) { + PyErr_Format(PyExc_SystemError, + "encoding %s not found", "utf-8"); + goto err; } + + if ((mainModule = PyImport_ImportModule("__main__")) == NULL) + goto err; + if ((mainDict = PyModule_GetDict(mainModule)) == NULL) + goto err; + + if ((name = PyString_FromString("tsunagari")) == NULL) + goto err; + if ((tsuModule = PyImport_ImportModule("tsunagari")) == NULL) + goto err; + if (PyObject_SetAttr(mainModule, name, tsuModule) < 0) + goto err; + + Py_DECREF(name); + Py_DECREF(tsuModule); + + // Disable builtin filesystem IO. + if (Py_InitModule("__builtin__", nullMethods) == NULL) + goto err; + + // Disable most Python system imports. + if (!add_worldfinder()) + goto err; + + // Add world to Python's sys.path. + if (!sysPathAppend(BASE_ZIP_PATH)) + goto err; + BOOST_FOREACH(std::string archive, conf.dataPath) + if (!sysPathAppend(archive)) + goto err; + return true; + +err: + Log::fatal("Python", "An error occured while populating the " + "Python modules:"); + Log::setVerbosity(V_NORMAL); // Assure message can be seen. + pythonErr(); + return false; } void pythonFinalize() { + Py_DECREF(mainModule); Py_Finalize(); } @@ -232,7 +205,7 @@ static std::string extractTracebackWLib(PyObject* exc, PyObject* val, return extract(formatted); } catch (bp::error_already_set) { - return ""; + return std::string(); } } @@ -293,52 +266,8 @@ void pythonErr() Log::err("Python", extractException(exc, val, tb)); } -bp::object pythonGlobals() +PyObject* pythonGlobals() { - return dictMain; -} - -PyCodeObject* pythonCompile(const char* fn, const char* code) -{ - PyArena *arena = PyArena_New(); - if (!arena) { - return NULL; - } - - mod_ty mod = PyParser_ASTFromString(code, fn, Py_file_input, NULL, arena); - if (!mod) { - Log::err("Python", - std::string(fn) + ": failed to parse"); - pythonErr(); - return NULL; - } - - PyCodeObject* co = PyAST_Compile(mod, fn, NULL, arena); - if (!co) { - Log::err("Python", - std::string(fn) + ": failed to compile"); - pythonErr(); - return NULL; - } - - PyArena_Free(arena); - return co; -} - -bool pythonExec(PyCodeObject* code) -{ - if (!code) - return false; - - // FIXME: locals, globals - PyObject* globals = dictMain.ptr(); - - inPythonScript++; - PyObject* result = PyEval_EvalCode(code, globals, globals); - inPythonScript--; - - if (!result) - pythonErr(); - return result; + return mainDict; } diff --git a/src/python.h b/src/python.h index 51a65b6..5c82949 100644 --- a/src/python.h +++ b/src/python.h @@ -43,8 +43,12 @@ #include #include +#include + + extern int inPythonScript; + //! Initialize Python libraries for use. bool pythonInit(); @@ -56,42 +60,46 @@ void pythonErr(); //! Access to global namespace shared by all Python scripts. -boost::python::object pythonGlobals(); +PyObject* pythonGlobals(); -//! Convenience function for binding a C++ object into the global Python -//! namespace. +//! Bind a C++ object into the global Python namespace. template -void pythonSetGlobal(const char* name, T pointer) +void pythonSetGlobal(const std::string& name, T pointer) { + using namespace boost::python; + + PyObject* globals = NULL; + PyObject* wrapper = NULL; + + if ((globals = pythonGlobals()) == NULL) + goto err; try { - pythonGlobals()[name] = boost::python::ptr(pointer); + wrapper = incref(converter::arg_to_python(pointer).get()); } catch (boost::python::error_already_set) { - pythonErr(); + goto err; } + + PyDict_SetItemString(globals, name.c_str(), wrapper); + Py_DECREF(wrapper); + + return; + +err: + pythonErr(); } template -void pythonAddFunction(const char* name, Fn fn) +void pythonAddFunction(const std::string& name, Fn fn) { using namespace boost::python; try { scope bltins(import("__builtin__")); - def(name, fn); + def(name.c_str(), fn); } catch (error_already_set) { pythonErr(); } } - -//! Compile a Python script. Must provide both a representative filename for -//! any error messages along with a string containing the body of code to -//! compile. Returns NULL on failure and prints any errors. -PyCodeObject* pythonCompile(const char* fn, const char* code); - -//! Run a compiled Python script. Returns false on runtime error and prints the -//! error. -bool pythonExec(PyCodeObject* code); - #endif diff --git a/src/pyworldfinder.cpp b/src/pyworldfinder.cpp new file mode 100644 index 0000000..1eb8455 --- /dev/null +++ b/src/pyworldfinder.cpp @@ -0,0 +1,165 @@ +/*************************************** +** Tsunagari Tile Engine ** +** pyworldfinder.cpp ** +** Copyright 2011-2013 PariahSoft LLC ** +***************************************/ + +// ********** +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// ********** + + +#include + +#include "log.h" +#include "reader.h" + + +//! List of known safe Python modules allowed for importing. +static const char* module_whitelist[] = { + "__builtin__", + "__main__", + "math", + "traceback", + NULL +}; + +static bool wf_in_whitelist(const char* name) +{ + for (int i = 0; module_whitelist[i]; i++) + if (!strcmp(name, module_whitelist[i])) + return true; + return false; +} + +static PyObject* wf_find_module(PyObject* /*self*/, PyObject* args) +{ + PyObject* fullname = NULL; + PyObject* path = NULL; // dummy + + if (!PyArg_UnpackTuple(args, "find_module", 1, 2, &fullname, &path)) { + // exc already set + return NULL; + } + if (!PyString_Check(fullname)) { + PyErr_Format(PyExc_TypeError, "expected string, got %.200s", + fullname->ob_type->tp_name); + return NULL; + } + + const char* dotname = PyString_AsString(fullname); // "foo.bar" + std::string slashname(dotname); // "foo/bar.py" + std::replace(slashname.begin(), slashname.end(), '.', '/'); + + // Returning Py_None allows the import to continue. NULL stops it. + if (wf_in_whitelist(dotname)) { + Py_INCREF(Py_None); + return Py_None; + } + else if (Reader::directoryExists(slashname) && Reader::fileExists(slashname + "/__init__.py")) { + Py_INCREF(Py_None); + return Py_None; + } + else if (Reader::resourceExists(slashname + ".py")) { + Py_INCREF(Py_None); + return Py_None; + } + else { + PyErr_Format(PyExc_ImportError, "No module name %.200s", dotname); + return NULL; + } +} + +typedef struct { + PyObject_HEAD +} worldfinderobject; + +static PyMethodDef pyworldfinder_methods[] = { + {"find_module", (PyCFunction)wf_find_module, METH_VARARGS, NULL}, + {NULL, NULL, 0, NULL}, +}; + +static bool initted = false; +static PyTypeObject worldfinder_type = { + PyObject_HEAD_INIT(&PyType_Type) +}; + +static bool wf_init_type() +{ + if (!initted) { + initted = false; + + worldfinder_type.tp_name = "worldfinder"; + worldfinder_type.tp_basicsize = sizeof(worldfinderobject); + worldfinder_type.tp_getattro = PyObject_GenericGetAttr; + worldfinder_type.tp_flags = Py_TPFLAGS_DEFAULT; + worldfinder_type.tp_methods = pyworldfinder_methods; + + if (PyType_Ready(&worldfinder_type) < 0) { + Py_FatalError("Can't initialize worldfinder type"); + return false; + } + } + return true; +} + +static PyObject* wf_worldfinder_new() +{ + wf_init_type(); + return (PyObject*)PyObject_New(worldfinderobject, &worldfinder_type); +} + + +/** Public functions **/ + +bool add_worldfinder() +{ + PyObject* meta_path = NULL; + PyObject* finder = NULL; + int idx = -1; + + /* meta_path is a borrowed reference; no decref */ + meta_path = PySys_GetObject((char*)"meta_path"); + if (meta_path == NULL || !PyList_Check(meta_path)) { + Log::fatal("Python", + "sys.meta_path must be a list of import hooks"); + goto err; + } + + finder = wf_worldfinder_new(); + if (finder == NULL) { + Log::fatal("Python", + "failed to create PyWorldFinder object"); + goto err; + } + + idx = PyList_Append(meta_path, finder); + if (idx == -1) { + Log::fatal("Python", "failed to append to sys.meta_path"); + goto err; + } + + Py_DECREF(finder); + return true; + +err: + Py_XDECREF(finder); + return false; +} + diff --git a/src/scriptinst.h b/src/pyworldfinder.h similarity index 54% copy from src/scriptinst.h copy to src/pyworldfinder.h index 9458820..3f28b14 100644 --- a/src/scriptinst.h +++ b/src/pyworldfinder.h @@ -1,10 +1,8 @@ -/********************************* -** Tsunagari Tile Engine ** -** scriptinst.h ** -** Copyright 2011-2012 OmegaSDG ** -*********************************/ - -// "OmegaSDG" is defined as Michael D. Reiley and Paul Merrill. +/********************************** +** Tsunagari Tile Engine ** +** pyworldfinder.h ** +** Copyright 2013 PariahSoft LLC ** +**********************************/ // ********** // Permission is hereby granted, free of charge, to any person obtaining a copy @@ -26,44 +24,12 @@ // IN THE SOFTWARE. // ********** -#ifndef SCRIPTINST_H -#define SCRIPTINST_H - -#include - -#include -#include - -class ScriptInst -{ -public: - ScriptInst(); - ScriptInst(const std::string& strloc); - ScriptInst(boost::python::object pyfn); - - // context = domain for errors msgs - bool validate(const std::string& context); - bool invoke(); - -private: - struct strref { - std::string filename; - std::string funcname; - PyCodeObject* funccall; - }; - - boost::variant< - void*, - strref, - boost::python::object - > data; - - friend struct validate_visitor; - friend struct invoke_visitor; - friend struct topython_visitor; - friend struct scriptinst_to_python; -}; - -void exportScriptInst(); - -#endif +/* Install a filter to Python's import statement that blocks illegal modules + * from being imported. All modules inside a world file are allowed, as are + * select packages from Python's base installation. Others are denied and will + * generate an ImportException in Python. + * + * This function does not add world files to Python's import path. This needs + * to happen seperately. + */ +bool add_worldfinder(); diff --git a/src/reader.cpp b/src/reader.cpp index 498710f..e253700 100644 --- a/src/reader.cpp +++ b/src/reader.cpp @@ -32,7 +32,6 @@ #include #include #include -#include #include #include #include @@ -40,6 +39,7 @@ #include #include +#include "bytecode.h" #include "client-conf.h" #include "log.h" #include "python.h" @@ -58,7 +58,7 @@ Cache images; Cache tiles; Cache sounds; Cache xmls; -Cache codes; +Cache bytecodes; Cache texts; // DTDs don't expire. No garbage collection. @@ -186,12 +186,12 @@ static XMLDoc* readXMLDoc(const std::string& name, static bool callInitpy(const std::string& archivePath) { ASSERT(Reader::prependPath(archivePath)); - bool exists = Reader::resourceExists("init.py"); + bool exists = Reader::resourceExists("__init__.py"); Log::info(archivePath, - std::string("init.py: ") + + std::string("__init__.py: ") + (exists ? "found" : "not found")); if (exists) - ASSERT(Reader::runPythonScript("init.py")); + ASSERT(Reader::runPythonScript("__init__.py")); ASSERT(Reader::rmPath(archivePath)); return true; } @@ -282,6 +282,16 @@ bool Reader::resourceExists(const std::string& name) return PHYSFS_exists(name.c_str()); } +bool Reader::directoryExists(const std::string& name) +{ + return resourceExists(name) && PHYSFS_isDirectory(name.c_str()); +} + +bool Reader::fileExists(const std::string& name) +{ + return resourceExists(name) && !PHYSFS_isDirectory(name.c_str()); +} + Gosu::Buffer* Reader::readBuffer(const std::string& name) { Gosu::Buffer* buf = new Gosu::Buffer(); @@ -307,7 +317,7 @@ ImageRef Reader::getImage(const std::string& name) if (existing) return existing; - BufferPtr buffer(readBuffer(name)); + boost::scoped_ptr buffer(readBuffer(name)); if (!buffer) return ImageRef(); @@ -326,7 +336,7 @@ TiledImageRef Reader::getTiledImage(const std::string& name, if (existing) return existing; - BufferPtr buffer(readBuffer(name)); + boost::scoped_ptr buffer(readBuffer(name)); if (!buffer) return TiledImageRef(); @@ -349,7 +359,7 @@ SampleRef Reader::getSample(const std::string& name) if (existing) return existing; - BufferPtr buffer(readBuffer(name)); + boost::scoped_ptr buffer(readBuffer(name)); if (!buffer) return SampleRef(); SampleRef result(new Sound(new Gosu::Sample(buffer->frontReader()))); @@ -371,18 +381,35 @@ XMLRef Reader::getXMLDoc(const std::string& name, return result; } -bool Reader::runPythonScript(const std::string& name) +BytecodeRef Reader::getBytecode(const std::string& path) { - PyCodeObject* existing = codes.momentaryRequest(name); + BytecodeRef existing = bytecodes.momentaryRequest(path); if (existing) - return pythonExec(existing); + return existing; - std::string code = readString(name); - PyCodeObject* result = code.size() ? - pythonCompile(name.c_str(), code.c_str()) : NULL; + BytecodeRef result; + if (fileExists(path)) { + std::string code = readString(path); + Bytecode* script = new Bytecode(path, code); + result.reset(script); + } - codes.momentaryPut(name, result); - return pythonExec(result); + bytecodes.momentaryPut(path, result); + return result; +} + +bool Reader::runPythonScript(const std::string& path) +{ + BytecodeRef bc = getBytecode(path); + if (bc && bc->valid()) { + bc->execute(); + return true; + } + else { + Log::err("Script", + bc->filename() + ": Attempt to run broken Python script"); + return false; + } } std::string Reader::getText(const std::string& name) diff --git a/src/reader.h b/src/reader.h index 98ec3ca..bc7bb96 100644 --- a/src/reader.h +++ b/src/reader.h @@ -34,8 +34,8 @@ #include #include #include -#include +#include "bytecode.h" #include "cache.h" #include "image.h" #include "sound.h" @@ -49,7 +49,6 @@ namespace Gosu { } // We hand out and manage resources in these forms: -typedef boost::scoped_ptr BufferPtr; typedef boost::shared_ptr SampleRef; /** @@ -72,6 +71,8 @@ public: //! Returns true if the World contains a resource by that name. static bool resourceExists(const std::string& name); + static bool directoryExists(const std::string& name); + static bool fileExists(const std::string& name); static Gosu::Buffer* readBuffer(const std::string& name); static std::string readString(const std::string& name); @@ -92,6 +93,8 @@ public: static XMLRef getXMLDoc(const std::string& name, const std::string& dtdPath); + static BytecodeRef getBytecode(const std::string& path); + //! Request a Python script from the World be run. static bool runPythonScript(const std::string& name); diff --git a/src/scriptinst.cpp b/src/scriptinst.cpp index 105a7fd..b0a6618 100644 --- a/src/scriptinst.cpp +++ b/src/scriptinst.cpp @@ -26,36 +26,28 @@ // IN THE SOFTWARE. // ********** +// from Python +#include + #include "log.h" #include "python.h" #include "reader.h" #include "scriptinst.h" + struct validate_visitor : public boost::static_visitor { - std::string context; - - validate_visitor(const std::string& context) : context(context) {} - - bool operator()(void*) const + bool operator()(BytecodeRef bc) const { - return true; - } - - bool operator()(ScriptInst::strref ref) const - { - if (ref.filename.empty()) { - Log::err(context, - "script filename is empty"); + if (!bc) { + Log::err("ScriptInst", ": script not valid"); return false; } - - if (!Reader::resourceExists(ref.filename)) { - Log::err(context, - ref.filename + ": script file not found"); + if (!bc->valid()) { + Log::err("ScriptInst", bc->filename() + + ": script not valid"); return false; } - return true; } @@ -65,114 +57,75 @@ struct validate_visitor : public boost::static_visitor } }; + struct invoke_visitor : public boost::static_visitor { - bool operator()(void*) const + bool operator()(BytecodeRef bc) const { - return true; + return (bc && bc->valid()) ? bc->execute() : false; } - bool operator()(ScriptInst::strref ref) const + bool operator()(boost::python::object callable) const { - if (ref.funcname.size()) { - Reader::runPythonScript(ref.filename); - return pythonExec(ref.funccall); - } - else { - return Reader::runPythonScript(ref.filename); - } - } - - bool operator()(boost::python::object pyfn) const - { - inPythonScript++; try { - pyfn(); + inPythonScript++; + callable(); inPythonScript--; return true; } catch (boost::python::error_already_set) { inPythonScript--; + // XXX: How does this interact with a C++/Python callstack? + //Log::err("Python", "Originating from " + source + ":"); pythonErr(); return false; } } }; -ScriptInst::ScriptInst() - : data((void*)NULL) -{ -} -ScriptInst::ScriptInst(const std::string& strloc) + +ScriptInst::ScriptInst(const std::string& source) + : data(Reader::getBytecode(source)) { - size_t colon = strloc.find(':'); - strref ref; - if (colon == std::string::npos) { - ref.filename = strloc; - } - else { - ref.filename = strloc.substr(0, colon); - ref.funcname = strloc.substr(colon + 1); - ref.funccall = pythonCompile( - "", - (ref.funcname).c_str() - ); + if (!validate()) { + Log::err("ScriptInst", "Error loading " + source); } - data = ref; } -ScriptInst::ScriptInst(boost::python::object pyfn) - : data(pyfn) + +ScriptInst::ScriptInst(boost::python::object callable) + : data(callable) { } -bool ScriptInst::validate(const std::string& context) + +bool ScriptInst::validate() { - return boost::apply_visitor(validate_visitor(context), data); + return boost::apply_visitor(validate_visitor(), data); } + bool ScriptInst::invoke() { return boost::apply_visitor(invoke_visitor(), data); } - - - - - - struct topython_visitor : public boost::static_visitor { - PyObject* operator()(void*) const + PyObject* operator()(BytecodeRef bc) const { - using namespace boost::python; - - return incref(Py_None); - } - - PyObject* operator()(ScriptInst::strref ref) const - { - using namespace boost::python; - - // Prevent compilation name collisions with "object" by making - // it "bp::object". - namespace bp = boost::python; - - bp::object str; - if (ref.funcname.size()) - str = bp::object(ref.filename + ":" + ref.funcname); + boost::python::object str; + if (bc) + str = boost::python::object(bc->filename()); else - str = bp::object(ref.filename); - return incref(str.ptr()); + str = boost::python::object(""); + return boost::python::incref(str.ptr()); } - PyObject* operator()(boost::python::object pyfn) const + PyObject* operator()(boost::python::object callable) const { - using namespace boost::python; - - return incref(pyfn.ptr()); + return boost::python::incref(callable.ptr()); } }; @@ -199,6 +152,8 @@ struct scriptinst_from_python // Can this be converted to a ScriptInst? static void* convertible(PyObject* obj) { + //bool callable = obj->ob_type->tp_call != NULL; + //const char* tp_name = obj->ob_type->tp_name; // XXX: Return non-NULL only if string or // callable (fn or lambda?). return obj; @@ -210,14 +165,12 @@ struct scriptinst_from_python PyObject* obj, boost::python::converter::rvalue_from_python_stage1_data* data) { - using namespace boost::python; - // Prevent compilation name collisions with "object" by making // it "bp::object". namespace bp = boost::python; void* storage = - ((converter::rvalue_from_python_storage*)data) + ((bp::converter::rvalue_from_python_storage*)data) ->storage.bytes; if (PyString_Check(obj)) { @@ -227,7 +180,7 @@ struct scriptinst_from_python else { // By default, the PyObject is a borrowed reference, // which means it hasn't been incref'd. - handle<> hndl(borrowed(obj)); + bp::handle<> hndl(bp::borrowed(obj)); new (storage) ScriptInst(bp::object(hndl)); } @@ -235,6 +188,7 @@ struct scriptinst_from_python } }; + void exportScriptInst() { using namespace boost::python; diff --git a/src/scriptinst.h b/src/scriptinst.h index 9458820..2b7a024 100644 --- a/src/scriptinst.h +++ b/src/scriptinst.h @@ -34,27 +34,21 @@ #include #include +#include "bytecode.h" + + class ScriptInst { public: - ScriptInst(); - ScriptInst(const std::string& strloc); - ScriptInst(boost::python::object pyfn); + ScriptInst(const std::string& source); + ScriptInst(boost::python::object callable); - // context = domain for errors msgs - bool validate(const std::string& context); + bool validate(); bool invoke(); private: - struct strref { - std::string filename; - std::string funcname; - PyCodeObject* funccall; - }; - boost::variant< - void*, - strref, + BytecodeRef, boost::python::object > data; @@ -64,6 +58,8 @@ private: friend struct scriptinst_to_python; }; + void exportScriptInst(); #endif + diff --git a/src/tile.cpp b/src/tile.cpp index f7e94ba..d1e01aa 100644 --- a/src/tile.cpp +++ b/src/tile.cpp @@ -172,21 +172,24 @@ void TileBase::setType(TileType* type) void TileBase::runEnterScript(Entity* triggeredBy) { - runScript(triggeredBy, enterScript); + if (enterScript) + runScript(triggeredBy, enterScript.get()); if (parent) parent->runEnterScript(triggeredBy); } void TileBase::runLeaveScript(Entity* triggeredBy) { - runScript(triggeredBy, leaveScript); + if (leaveScript) + runScript(triggeredBy, leaveScript.get()); if (parent) parent->runLeaveScript(triggeredBy); } void TileBase::runUseScript(Entity* triggeredBy) { - runScript(triggeredBy, useScript); + if (useScript) + runScript(triggeredBy, useScript.get()); if (parent) parent->runUseScript(triggeredBy); } diff --git a/src/tile.h b/src/tile.h index d6ab0e8..877e3f5 100644 --- a/src/tile.h +++ b/src/tile.h @@ -187,7 +187,7 @@ private: public: TileBase* parent; unsigned flags; - ScriptInst enterScript, leaveScript, useScript; + boost::optional enterScript, leaveScript, useScript; }; //! Contains properties unique to this tile. diff --git a/src/world.cpp b/src/world.cpp index c6a4c69..dda785f 100644 --- a/src/world.cpp +++ b/src/world.cpp @@ -70,7 +70,8 @@ bool World::init() view.reset(new Viewport(viewportSz)); view->trackEntity(&player); - loadScript.invoke(); + if (loadScript) + loadScript->invoke(); Area* area = getArea(startArea); if (area == NULL) { @@ -103,7 +104,8 @@ void World::buttonDown(const Gosu::Button btn) default: if (!paused && keyStates.empty()) { area->buttonDown(btn); - keydownScript.invoke(); + if (keydownScript) + keydownScript->invoke(); } break; } @@ -117,7 +119,8 @@ void World::buttonUp(const Gosu::Button btn) default: if (!paused && keyStates.empty()) { area->buttonUp(btn); - keyupScript.invoke(); + if (keyupScript) + keyupScript->invoke(); } break; } @@ -270,7 +273,8 @@ void World::restoreKeys() void World::runAreaLoadScript(Area* area) { pythonSetGlobal("Area", area); - areaLoadScript.invoke(); + if (areaLoadScript) + areaLoadScript->invoke(); } time_t World::calculateDt(time_t now) @@ -439,13 +443,13 @@ bool World::processScript(XMLNode node) ScriptInst script(filename); if (node.is("on_init")) { - if (!script.validate("world.conf")) + if (!script.validate()) return false; - loadScript = filename; + loadScript = script; } else if (node.is("on_area_init")) { - if (!script.validate("world.conf")) + if (!script.validate()) return false; - areaLoadScript = filename; + areaLoadScript = script; } } return true; diff --git a/src/world.h b/src/world.h index 9de2eac..9449581 100644 --- a/src/world.h +++ b/src/world.h @@ -147,7 +147,7 @@ public: void runAreaLoadScript(Area* area); - ScriptInst keydownScript, keyupScript; + boost::optional keydownScript, keyupScript; protected: /** @@ -197,8 +197,8 @@ protected: vicoord startCoords; - ScriptInst loadScript; - ScriptInst areaLoadScript; + boost::optional loadScript; + boost::optional areaLoadScript; ImageRef pauseInfo; -- 2.11.4.GIT