First step for restricted (and therefore safe) Python in trunk.

For script authors this means:

- no more exceptions (try..except)

- no more unsafe builtins (eval, file, type, ...)

- no more import (some whitelisted modules are auto-imported currently)

- no more access to internals (e.g. __dict__, __name__, ...)

The campaign server for now still doesn't allow .py scripts, but
likely this restriction can be removed.
This commit is contained in:
Elias Pschernig 2007-03-18 16:26:22 +00:00
parent fe3ee09b27
commit a0bf9a7209
4 changed files with 476 additions and 166 deletions

359
data/ais/safe.py Normal file
View file

@ -0,0 +1,359 @@
"""An attempt at creating a safe_exec for python.
This file is public domain and is not suited for any serious purpose.
This code is not guaranteed to work. Use at your own risk!
Beware! Trust no one!
Please e-mail philhassey@yahoo.com if you find any security holes.
Known limitations:
- Safe doesn't have any testing for timeouts/DoS. One-liners
like these will lock up the system: "while 1: pass", "234234**234234"
- Lots of (likely) safe builtins and safe AST Nodes are not allowed.
I suppose you can add them to the whitelist if you want them. I
trimmed it down as much as I thought I could get away with and still
have useful python code.
- Might not work with future versions of python - this is made with
python 2.4 in mind. _STR_NOT_BEGIN might have to be extended
in the future with more magic variable prefixes. Or you can
switch to conservative mode, but then even variables like "my_var"
won't work, which is sort of a nuisance.
- If you get data back from a safe_exec, don't call any functions
or methods - they might not be safe with __builtin__ restored
to its normal state. Work with them again via an additional safe_exec.
- The "context" sent to the functions is not tested at all. If you
pass in a dangerous function {'myfile':file} the code will be able
to call it.
"""
# Built-in Objects
# http://docs.python.org/lib/builtin.html
# AST Nodes - compiler
# http://docs.python.org/lib/module-compiler.ast.html
# Types and members - inspection
# http://docs.python.org/lib/inspect-types.html
# The standard type heirarchy
# http://docs.python.org/ref/types.html
# Based loosely on - Restricted "safe" eval - by Babar K. Zafar
# (it isn't very safe, but it got me started)
# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/496746
# Securing Python: Controlling the abilities of the interpreter
# (or - why even trying this is likely to end in tears)
# http://us.pycon.org/common/talkdata/PyCon2007/062/PyCon_2007.pdf
# Changes
# 2007-03-13: added test for unicode strings that contain __, etc
# 2007-03-09: renamed safe_eval to safe_exec, since that's what it is.
# 2007-03-09: use "exec code in context" , because of test_misc_recursive_fnc
# 2007-03-09: Removed 'type' from _BUILTIN_OK - see test_misc_type_escape
# 2007-03-08: Cleaned up the destroy / restore mechanism, added more tests
# 2007-03-08: Fixed how contexts work.
# 2007-03-07: Added test for global node
# 2007-03-07: Added test for SyntaxError
# 2007-03-07: Fixed an issue where the context wasn't being reset (added test)
# 2007-03-07: Added unittest for dir()
# 2007-03-07: Removed 'isinstance', 'issubclass' from builtins whitelist
# 2007-03-07: Removed 'EmptyNode', 'Global' from AST whitelist
# 2007-03-07: Added import __builtin__; s/__builtins__/__builtin__
import compiler
import __builtin__
class SafeException(Exception):
"""Base class for Safe Exceptions"""
def __init__(self,*value):
self.value = str(value)
def __str__(self):
return self.value
class CheckNodeException(SafeException):
"""AST Node class is not in the whitelist."""
pass
class CheckStrException(SafeException):
"""A string in the AST looks insecure."""
pass
class RunBuiltinException(SafeException):
"""During the run a non-whitelisted builtin was called."""
pass
_NODE_CLASS_OK = [
'Add', 'And', 'AssAttr', 'AssList', 'AssName', 'AssTuple',
'Assert', 'Assign','AugAssign', 'Bitand', 'Bitor', 'Bitxor', 'Break',
'CallFunc', 'Class', 'Compare', 'Const', 'Continue',
'Dict', 'Discard', 'Div', 'Ellipsis', 'Expression', 'FloorDiv',
'For', 'Function', 'Getattr', 'If', 'Keyword',
'LeftShift', 'List', 'ListComp', 'ListCompFor', 'ListCompIf', 'Mod',
'Module', 'Mul', 'Name', 'Node', 'Not', 'Or', 'Pass', 'Power',
'Print', 'Printnl', 'Return', 'RightShift', 'Slice', 'Sliceobj',
'Stmt', 'Sub', 'Subscript', 'Tuple', 'UnaryAdd', 'UnarySub', 'While',
]
_NODE_ATTR_OK = []
_STR_OK = ['__init__']
_STR_NOT_CONTAIN = ['__']
_STR_NOT_BEGIN = ['im_','func_','tb_','f_','co_',]
## conservative settings
#_NODE_ATTR_OK = ['flags']
#_STR_NOT_CONTAIN = ['_']
#_STR_NOT_BEGIN = []
def _check_node(node):
if node.__class__.__name__ not in _NODE_CLASS_OK:
raise CheckNodeException(node.lineno,node.__class__.__name__)
for k,v in node.__dict__.items():
if k in _NODE_ATTR_OK: continue
if v in _STR_OK: continue
if type(v) not in [str,unicode]: continue
for s in _STR_NOT_CONTAIN:
if s in v: raise CheckStrException(node.lineno,k,v)
for s in _STR_NOT_BEGIN:
if v[:len(s)] == s: raise CheckStrException(node.lineno,k,v)
for child in node.getChildNodes():
_check_node(child)
def _check_ast(code):
ast = compiler.parse(code)
_check_node(ast)
# r = [v for v in dir(__builtin__) if v[0] != '_' and v[0] == v[0].upper()] ; r.sort() ; print r
_BUILTIN_OK = [
'__debug__','quit','exit',
'ArithmeticError', 'AssertionError', 'AttributeError', 'DeprecationWarning', 'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False', 'FloatingPointError', 'FutureWarning', 'IOError', 'ImportError', 'IndentationError', 'IndexError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'NameError', 'None', 'NotImplemented', 'NotImplementedError', 'OSError', 'OverflowError', 'OverflowWarning', 'PendingDeprecationWarning', 'ReferenceError', 'RuntimeError', 'RuntimeWarning', 'StandardError', 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError', 'True', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError', 'UserWarning', 'ValueError', 'Warning', 'ZeroDivisionError',
'abs', 'bool', 'cmp', 'complex', 'dict', 'divmod', 'filter', 'float', 'frozenset', 'hex', 'int', 'len', 'list', 'long', 'map', 'max', 'min', 'object', 'oct', 'pow', 'range', 'reduce', 'repr', 'round', 'set', 'slice', 'str', 'sum', 'tuple', 'xrange', 'zip',
'isinstance', 'issubclass']
#this is zope's list...
#in ['False', 'None', 'True', 'abs', 'basestring', 'bool', 'callable',
#'chr', 'cmp', 'complex', 'divmod', 'float', 'hash',
#'hex', 'id', 'int', 'isinstance', 'issubclass', 'len',
#'long', 'oct', 'ord', 'pow', 'range', 'repr', 'round',
#'str', 'tuple', 'unichr', 'unicode', 'xrange', 'zip']:
_BUILTIN_STR = [
'copyright','credits','license','__name__','__doc__',
]
def _builtin_fnc(k):
def fnc(*vargs,**kargs):
raise RunBuiltinException(k)
return fnc
_builtin_globals = None
_builtin_globals_r = None
def _builtin_init():
global _builtin_globals, _builtin_globals_r
if _builtin_globals != None: return
_builtin_globals_r = __builtin__.__dict__.copy()
r = _builtin_globals = {}
for k in __builtin__.__dict__.keys():
v = None
if k in _BUILTIN_OK: v = __builtin__.__dict__[k]
elif k in _BUILTIN_STR: v = ''
else: v = _builtin_fnc(k)
r[k] = v
def _builtin_destroy():
_builtin_init()
for k,v in _builtin_globals.items():
__builtin__.__dict__[k] = v
def _builtin_restore():
for k,v in _builtin_globals_r.items():
__builtin__.__dict__[k] = v
def safe_check(code):
"""Check the code to be safe."""
return _check_ast(code)
def safe_run(code,context=None):
"""Exec code with only safe builtins on."""
if context == None: context = {}
_builtin_destroy()
try:
#exec code in _builtin_globals,context
context['__builtins__'] = _builtin_globals
exec code in context
_builtin_restore()
except:
_builtin_restore()
raise
def safe_exec(code,context = None):
"""Check the code to be safe, then run it with only safe builtins on."""
safe_check(code)
safe_run(code,context)
if __name__ == '__main__':
import unittest
class TestSafe(unittest.TestCase):
def test_check_node_import(self):
self.assertRaises(CheckNodeException,safe_exec,"import os")
def test_check_node_from(self):
self.assertRaises(CheckNodeException,safe_exec,"from os import *")
def test_check_node_exec(self):
self.assertRaises(CheckNodeException,safe_exec,"exec 'None'")
def test_check_node_raise(self):
self.assertRaises(CheckNodeException,safe_exec,"raise Exception")
def test_check_node_global(self):
self.assertRaises(CheckNodeException,safe_exec,"global abs")
def test_check_str_x(self):
self.assertRaises(CheckStrException,safe_exec,"x__ = 1")
def test_check_str_str(self):
self.assertRaises(CheckStrException,safe_exec,"x = '__'")
def test_check_str_class(self):
self.assertRaises(CheckStrException,safe_exec,"None.__class__")
def test_check_str_func_globals(self):
self.assertRaises(CheckStrException,safe_exec,"def x(): pass; x.func_globals")
def test_check_str_init(self):
safe_exec("def __init__(self): pass")
def test_check_str_subclasses(self):
self.assertRaises(CheckStrException,safe_exec,"object.__subclasses__")
def test_check_str_properties(self):
code = """
class X(object):
def __get__(self,k,t=None):
1/0
"""
self.assertRaises(CheckStrException,safe_exec,code)
def test_check_str_unicode(self):
self.assertRaises(CheckStrException,safe_exec,"u'__'")
def test_run_builtin_open(self):
self.assertRaises(RunBuiltinException,safe_exec,"open('test.txt','w')")
def test_run_builtin_getattr(self):
self.assertRaises(RunBuiltinException,safe_exec,"getattr(None,'x')")
def test_run_builtin_abs(self):
safe_exec("abs(-1)")
def test_run_builtin_open_fnc(self):
def test():
f = open('test.txt','w')
self.assertRaises(RunBuiltinException,safe_exec,"test()",{'test':test})
def test_run_builtin_open_context(self):
#this demonstrates how python jumps into some mystical
#restricted mode at this point .. causing this to throw
#an IOError. a bit strange, if you ask me.
self.assertRaises(IOError,safe_exec,"test('test.txt','w')",{'test':open})
def test_run_builtin_type_context(self):
#however, even though this is also a very dangerous function
#python's mystical restricted mode doesn't throw anything.
safe_exec("test(1)",{'test':type})
def test_run_builtin_dir(self):
self.assertRaises(RunBuiltinException,safe_exec,"dir(None)")
def test_run_exeception_div(self):
self.assertRaises(ZeroDivisionError,safe_exec,"1/0")
def test_run_exeception_i(self):
self.assertRaises(ValueError,safe_exec,"(-1)**0.5")
def test_misc_callback(self):
self.value = None
def test(): self.value = 1
safe_exec("test()", {'test':test})
self.assertEqual(self.value, 1)
def test_misc_safe(self):
self.value = None
def test(v): self.value = v
code = """
class Test:
def __init__(self,value):
self.x = value
self.y = 4
def run(self):
for n in xrange(0,34):
self.x += n
self.y *= n
return self.x+self.y
b = Test(value)
r = b.run()
test(r)
"""
safe_exec(code,{'value':3,'test':test})
self.assertEqual(self.value, 564)
def test_misc_context_reset(self):
#test that local contact is reset
safe_exec("abs = None")
safe_exec("abs(-1)")
safe_run("abs = None")
safe_run("abs(-1)")
def test_misc_syntax_error(self):
self.assertRaises(SyntaxError,safe_exec,"/")
def test_misc_context_switch(self):
self.value = None
def test(v): self.value = v
safe_exec("""
def test2():
open('test.txt','w')
test(test2)
""",{'test':test})
self.assertRaises(RunBuiltinException,safe_exec,"test()",{'test':self.value})
def test_misc_context_junk(self):
#test that stuff isn't being added into *my* context
#except what i want in it..
c = {}
safe_exec("b=1",c)
self.assertEqual(c['b'],1)
def test_misc_context_later(self):
#honestly, i'd rec that people don't do this, but
#at least we've got it covered ...
c = {}
safe_exec("def test(): open('test.txt','w')",c)
self.assertRaises(RunBuiltinException,c['test'])
#def test_misc_test(self):
#code = "".join(open('test.py').readlines())
#safe_check(code)
def test_misc_builtin_globals_write(self):
#check that a user can't modify the special _builtin_globals stuff
safe_exec("abs = None")
self.assertNotEqual(_builtin_globals['abs'],None)
#def test_misc_builtin_globals_used(self):
##check that the same builtin globals are always used
#c1,c2 = {},{}
#safe_exec("def test(): pass",c1)
#safe_exec("def test(): pass",c2)
#self.assertEqual(c1['test'].func_globals,c2['test'].func_globals)
#self.assertEqual(c1['test'].func_globals,_builtin_globals)
def test_misc_builtin_globals_used(self):
#check that the same builtin globals are always used
c = {}
safe_exec("def test1(): pass",c)
safe_exec("def test2(): pass",c)
self.assertEqual(c['test1'].func_globals,c['test2'].func_globals)
self.assertEqual(c['test1'].func_globals['__builtins__'],_builtin_globals)
self.assertEqual(c['__builtins__'],_builtin_globals)
def test_misc_type_escape(self):
#tests that 'type' isn't allowed anymore
#with type defined, you could create magical classes like this:
code = """
def delmethod(self): 1/0
foo=type('Foo', (object,), {'_' + '_del_' + '_':delmethod})()
foo.error
"""
try:
self.assertRaises(RunBuiltinException,safe_exec,code)
finally:
pass
def test_misc_recursive_fnc(self):
code = "def test():test()\ntest()"
self.assertRaises(RuntimeError,safe_exec,code)
unittest.main()
#safe_exec('print locals()')

View file

@ -1,5 +1,4 @@
#!WPY
import wesnoth, heapq, random
"""This is a rather simple minded example of a python AI."""
@ -162,10 +161,10 @@ class AI:
score = -score
if unit in units and enemy in enemies:
try:
loc = wesnoth.move_unit(unit, tile)
except ValueError:
loc = None
#try:
loc = wesnoth.move_unit(unit, tile)
#except ValueError:
# loc = None
if loc == tile:
e = wesnoth.get_units()[enemy]
wesnoth.attack_unit(tile, enemy)

View file

@ -11,6 +11,22 @@
See the COPYING file for more details.
*/
/* Note about Python exceptions:
Originally, runtime exceptions were used for things like using out of bounds
coordinates or moving a unit which already got killed, and were supposed to
be catched by scripts. The subset of the Python language allowed in
scripts now has no exceptions anymore, to keep things simple. Therefore the
API was changed so scripts are not supposed to catch exceptions anymore.
This means, invalid positions or units will now only be indicated by things
like return values, but are ignored otherwise.
Most API functions still will cause exceptions (like if the game ends, or
the wrong number of parameters is passed to a function) - but those are not
supposed to be catched by user scripts.
*/
#ifdef HAVE_PYTHON
#include "global.hpp"
@ -33,18 +49,15 @@ static python_ai* running_instance;
bool python_ai::init_ = false;
PyObject* python_ai::python_error_ = NULL;
#define MSG_UNIT "Invalid unit"
void python_ai::set_error(const char *fmt, ...)
{
char buf[1024];
va_list arg;
#define return_none do {Py_INCREF(Py_None); return Py_None;} while(false)
va_start(arg, fmt);
vsnprintf(buf, sizeof(buf), fmt, arg);
va_end(arg);
PyErr_SetString(PyExc_ValueError, buf);
}
#define wrap(code) \
try { code } \
catch(end_level_exception& e) { \
running_instance->exception = e; \
PyErr_SetString(PyExc_RuntimeError, "C++ exception!"); \
return NULL; \
}
static PyObject* wrap_unittype(const unit_type& type);
static PyObject* wrap_attacktype(const attack_type& type);
@ -121,7 +134,7 @@ static PyGetSetDef unittype_getseters[] = {
PyObject* python_ai::unittype_advances_to( wesnoth_unittype* type, PyObject* args )
{
if ( !PyArg_ParseTuple( args, "" ) )
if (!PyArg_ParseTuple(args, "" ))
return NULL;
PyObject* list = PyList_New(type->unit_type_->advances_to().size());
@ -416,135 +429,96 @@ static PyObject* wrap_attacktype(const attack_type& type)
return (PyObject*)attack;
}
#define u_check if (!running_instance->is_unit_valid(unit->unit_)) return_none
bool python_ai::is_unit_valid(const unit* unit, bool do_set_error)
bool python_ai::is_unit_valid(const unit* unit)
{
if (!unit)
{
if (do_set_error)
set_error("Undefined unit.");
return false;
}
for(unit_map::const_iterator i = running_instance->get_info().units.begin(); i != running_instance->get_info().units.end(); ++i) {
if (unit == &i->second)
return true;
}
if (do_set_error)
set_error("Unit was destroyed.");
return false;
}
static PyObject* unit_get_name(wesnoth_unit* unit, void* /*closure*/)
{
if (!running_instance->is_unit_valid(unit->unit_)){
running_instance->set_error(MSG_UNIT);
return NULL;
}
return Py_BuildValue("s",( const char* )unit->unit_->name().c_str());
u_check;
return Py_BuildValue("s",( const char* )unit->unit_->name().c_str());
}
static PyObject* unit_is_enemy(wesnoth_unit* unit, void* /*closure*/)
{
if (!running_instance->is_unit_valid(unit->unit_)){
running_instance->set_error(MSG_UNIT);
return NULL;
}
return Py_BuildValue("i",running_instance->current_team().is_enemy(unit->unit_->side()) == true ? 1 : 0);
u_check;
return Py_BuildValue("i", running_instance->current_team().is_enemy(unit->unit_->side()) == true ? 1 : 0);
}
static PyObject* unit_can_recruit(wesnoth_unit* unit, void* /*closure*/)
{
if (!running_instance->is_unit_valid(unit->unit_)){
running_instance->set_error(MSG_UNIT);
return NULL;
}
return Py_BuildValue("i",unit->unit_->can_recruit() == true ? 1 : 0);
u_check;
return Py_BuildValue("i", unit->unit_->can_recruit() == true ? 1 : 0);
}
static PyObject* unit_side(wesnoth_unit* unit, void* /*closure*/)
{
if (!running_instance->is_unit_valid(unit->unit_)){
running_instance->set_error(MSG_UNIT);
return NULL;
}
return Py_BuildValue("i",unit->unit_->side());
u_check;
return Py_BuildValue("i", unit->unit_->side());
}
static PyObject* unit_movement_left(wesnoth_unit* unit, void* /*closure*/)
{
if (!running_instance->is_unit_valid(unit->unit_)){
running_instance->set_error(MSG_UNIT);
return NULL;
}
return Py_BuildValue("i",unit->unit_->movement_left());
u_check;
return Py_BuildValue("i", unit->unit_->movement_left());
}
static PyObject* unit_can_attack(wesnoth_unit* unit, void* /*closure*/)
{
if (!running_instance->is_unit_valid(unit->unit_)){
running_instance->set_error(MSG_UNIT);
return NULL;
}
return Py_BuildValue("i",unit->unit_->attacks_left());
u_check;
return Py_BuildValue("i", unit->unit_->attacks_left());
}
static PyObject* unit_hitpoints(wesnoth_unit* unit, void* /*closure*/)
{
if (!running_instance->is_unit_valid(unit->unit_)){
running_instance->set_error(MSG_UNIT);
return NULL;
}
return Py_BuildValue("i",(int)unit->unit_->hitpoints());
u_check;
return Py_BuildValue("i", (int)unit->unit_->hitpoints());
}
static PyObject* unit_max_hitpoints(wesnoth_unit* unit, void* /*closure*/)
{
if (!running_instance->is_unit_valid(unit->unit_)){
running_instance->set_error(MSG_UNIT);
return NULL;
}
return Py_BuildValue("i",(int)unit->unit_->max_hitpoints());
u_check;
return Py_BuildValue("i", (int)unit->unit_->max_hitpoints());
}
static PyObject* unit_experience(wesnoth_unit* unit, void* /*closure*/)
{
if (!running_instance->is_unit_valid(unit->unit_)){
running_instance->set_error(MSG_UNIT);
return NULL;
}
return Py_BuildValue("i",(int)unit->unit_->experience());
u_check;
return Py_BuildValue("i", (int)unit->unit_->experience());
}
static PyObject* unit_max_experience(wesnoth_unit* unit, void* /*closure*/)
{
if (!running_instance->is_unit_valid(unit->unit_)){
running_instance->set_error(MSG_UNIT);
return NULL;
}
return Py_BuildValue("i",(int)unit->unit_->max_experience());
u_check;
return Py_BuildValue("i", (int)unit->unit_->max_experience());
}
static PyObject* unit_poisoned(wesnoth_unit* unit, void* /*closure*/)
{
if (!running_instance->is_unit_valid(unit->unit_)){
running_instance->set_error(MSG_UNIT);
return NULL;
}
return Py_BuildValue("i",utils::string_bool(unit->unit_->get_state("poisoned")));
u_check;
return Py_BuildValue("i", utils::string_bool(unit->unit_->get_state("poisoned")));
}
static PyObject* unit_stoned(wesnoth_unit* unit, void* /*closure*/)
{
if (!running_instance->is_unit_valid(unit->unit_)){
running_instance->set_error(MSG_UNIT);
return NULL;
}
return Py_BuildValue("i",utils::string_bool(unit->unit_->get_state("stoned")));
u_check;
return Py_BuildValue("i", utils::string_bool(unit->unit_->get_state("stoned")));
}
static PyObject* unit_query_valid(wesnoth_unit* unit, void* /*closure*/)
{
return Py_BuildValue("i",running_instance->is_unit_valid(unit->unit_, false) == true ? 1 : 0);
return Py_BuildValue("i", running_instance->is_unit_valid(unit->unit_) == true ? 1 : 0);
}
static PyGetSetDef unit_getseters[] = {
@ -580,24 +554,18 @@ static PyGetSetDef unit_getseters[] = {
static PyObject* wrapper_unit_type( wesnoth_unit* unit, PyObject* args )
{
if ( !PyArg_ParseTuple( args, "" ) )
return NULL;
if (!running_instance->is_unit_valid(unit->unit_)){
running_instance->set_error(MSG_UNIT);
return NULL;
}
if (!PyArg_ParseTuple(args, ""))
return NULL;
u_check;
wassert(unit->unit_->type());
return wrap_unittype(*unit->unit_->type());
}
static PyObject* wrapper_unit_attacks( wesnoth_unit* unit, PyObject* args )
{
if ( !PyArg_ParseTuple( args, "" ) )
return NULL;
if (!running_instance->is_unit_valid(unit->unit_)){
running_instance->set_error(MSG_UNIT);
return NULL;
}
if (!PyArg_ParseTuple(args, ""))
return NULL;
u_check;
PyObject* list = PyList_New(unit->unit_->attacks().size());
for ( size_t attack = 0; attack < unit->unit_->attacks().size(); attack++)
PyList_SetItem(list,attack,wrap_attacktype(unit->unit_->attacks()[attack]));
@ -609,10 +577,7 @@ static PyObject* wrapper_unit_damage_from( wesnoth_unit* unit, PyObject* args )
wesnoth_attacktype* attack;
if ( !PyArg_ParseTuple( args, "O!", &wesnoth_attacktype_type, &attack ) )
return NULL;
if (!running_instance->is_unit_valid(unit->unit_)){
running_instance->set_error(MSG_UNIT);
return NULL;
}
u_check;
static gamemap::location no_loc;
return Py_BuildValue("i",unit->unit_->damage_from(*attack->attack_type_,true,no_loc));
}
@ -620,7 +585,7 @@ static PyObject* wrapper_unit_damage_from( wesnoth_unit* unit, PyObject* args )
static PyObject* wrapper_unittype_resistance_against( wesnoth_unittype* type, PyObject* args )
{
wesnoth_attacktype* attack;
if ( !PyArg_ParseTuple( args, "O!", &wesnoth_attacktype_type, &attack ) )
if (!PyArg_ParseTuple(args, "O!", &wesnoth_attacktype_type, &attack))
return NULL;
return Py_BuildValue("i",type->unit_type_->movement_type().resistance_against(*attack->attack_type_));
}
@ -1311,26 +1276,20 @@ PyObject* python_ai::wrapper_get_units(PyObject* /*self*/, PyObject* args)
PyObject* python_ai::wrapper_get_location(PyObject* /*self*/, PyObject* args)
{
int x, y;
if ( !PyArg_ParseTuple( args, "ii", &x, &y ) )
int x, y;
if (!PyArg_ParseTuple( args, "ii", &x, &y ))
return NULL;
if(x < 0 || x >= running_instance->get_info().map.x()){
running_instance->set_error("Invalid x value %d", x);
return NULL;
}
if(y < 0 || y >= running_instance->get_info().map.y()){
running_instance->set_error("Invalid y value %d", y);
return NULL;
}
if (x < 0 || x >= running_instance->get_info().map.x()) return_none;
if (y < 0 || y >= running_instance->get_info().map.y()) return_none;
gamemap::location loc(x,y);
return wrap_location(loc);
return wrap_location(loc);
}
PyObject* python_ai::wrapper_get_map(PyObject* /*self*/, PyObject* args)
{
if ( !PyArg_ParseTuple( args, "" ) )
return NULL;
if (!PyArg_ParseTuple(args, "" )) return NULL;
wesnoth_gamemap* map = (wesnoth_gamemap*)PyObject_NEW(wesnoth_gamemap, &wesnoth_gamemap_type);
map->map_ = &running_instance->get_info().map;
return (PyObject*)map;
@ -1338,8 +1297,7 @@ PyObject* python_ai::wrapper_get_map(PyObject* /*self*/, PyObject* args)
PyObject* python_ai::wrapper_get_teams(PyObject* /*self*/, PyObject* args)
{
if ( !PyArg_ParseTuple( args, "" ) )
return NULL;
if (!PyArg_ParseTuple(args, "" )) return NULL;
PyObject* list = PyList_New(running_instance->get_info().teams.size());
wesnoth_team* the_team;
@ -1386,8 +1344,9 @@ PyObject* python_ai::wrapper_move_unit(PyObject* /*self*/, PyObject* args)
{
wesnoth_location* from;
wesnoth_location* to;
if ( !PyArg_ParseTuple( args, "O!O!", &wesnoth_location_type, &from, &wesnoth_location_type, &to ) )
return NULL;
if (!PyArg_ParseTuple( args, "O!O!", &wesnoth_location_type, &from,
&wesnoth_location_type, &to ) )
return NULL;
bool valid = false;
ai_interface::move_map::const_iterator pos;
@ -1400,36 +1359,30 @@ PyObject* python_ai::wrapper_move_unit(PyObject* /*self*/, PyObject* args)
break;
}
}
if (!valid)
{
set_error("No unit at specified position.");
return NULL;
}
if (pos == running_instance->src_dst_.end())
{
set_error("This unit can't go to specified destination.");
return NULL;
}
if (!valid) return_none;
if (pos == running_instance->src_dst_.end()) return_none;
location location;
try {
wrap(
location = running_instance->move_unit_partial(
*from->location_, *to->location_, running_instance->possible_moves_);
}
catch(end_level_exception& e) {
running_instance->exception = e;
PyErr_SetString(PyExc_RuntimeError, "Game aborted!");
return NULL;
}
)
PyObject* loc = wrap_location(location);
running_instance->src_dst_.clear();
running_instance->dst_src_.clear();
running_instance->possible_moves_.clear();
running_instance->calculate_possible_moves(running_instance->possible_moves_,running_instance->src_dst_,running_instance->dst_src_,false);
running_instance->calculate_possible_moves(
running_instance->possible_moves_,
running_instance->src_dst_,
running_instance->dst_src_,false);
running_instance->enemy_src_dst_.clear();
running_instance->enemy_dst_src_.clear();
running_instance->enemy_possible_moves_.clear();
running_instance->calculate_possible_moves(running_instance->enemy_possible_moves_,running_instance->enemy_src_dst_,running_instance->enemy_dst_src_,true);
running_instance->calculate_possible_moves(
running_instance->enemy_possible_moves_,
running_instance->enemy_src_dst_,
running_instance->enemy_dst_src_,true);
return loc;
}
@ -1438,16 +1391,15 @@ PyObject* python_ai::wrapper_attack_unit(PyObject* /*self*/, PyObject* args)
wesnoth_location* from;
wesnoth_location* to;
int weapon = -1; // auto-choose
if ( !PyArg_ParseTuple( args, "O!O!|i", &wesnoth_location_type, &from, &wesnoth_location_type, &to, &weapon ) )
return NULL;
if (!PyArg_ParseTuple( args, "O!O!|i", &wesnoth_location_type, &from,
&wesnoth_location_type, &to, &weapon ) )
return NULL;
// FIXME: Remove this check and let the C++ code do the check if the attack
// is valid at all (there may be ranged attacks or similar later, and then
// the below will horribly fail).
if (!tiles_adjacent(*from->location_, *to->location_)) {
Py_INCREF(Py_None);
return Py_None;
}
if (!tiles_adjacent(*from->location_, *to->location_))
return_none;
info& inf = running_instance->get_info();
@ -1461,16 +1413,12 @@ PyObject* python_ai::wrapper_attack_unit(PyObject* /*self*/, PyObject* args)
*to->location_,
weapon);
try {
wrap(
running_instance->attack_enemy(*from->location_,*to->location_,
bc.get_attacker_stats().attack_num,
bc.get_defender_stats().attack_num);
}
catch(end_level_exception& e) {
running_instance->exception = e;
PyErr_SetString(PyExc_RuntimeError, "Game is won!");
return NULL;
}
)
running_instance->src_dst_.clear();
running_instance->dst_src_.clear();
running_instance->possible_moves_.clear();
@ -1487,8 +1435,8 @@ PyObject* python_ai::wrapper_attack_unit(PyObject* /*self*/, PyObject* args)
PyObject* python_ai::wrapper_get_adjacent_tiles(PyObject* /*self*/, PyObject* args)
{
wesnoth_location* where;
if ( !PyArg_ParseTuple( args, "O!", &wesnoth_location_type, &where ) )
return NULL;
if (!PyArg_ParseTuple( args, "O!", &wesnoth_location_type, &where))
return NULL;
gamemap const &map = running_instance->get_info().map;
PyObject* list = PyList_New(0);
@ -1504,14 +1452,16 @@ PyObject* python_ai::wrapper_recruit_unit(PyObject* /*self*/, PyObject* args)
{
wesnoth_location* where;
const char* name;
if ( !PyArg_ParseTuple( args, "sO!", &name, &wesnoth_location_type, &where ) )
return NULL;
return Py_BuildValue("i", running_instance->recruit(name,*where->location_) == true ? 1 : 0);
if (!PyArg_ParseTuple( args, "sO!", &name, &wesnoth_location_type, &where))
return NULL;
wrap(
return Py_BuildValue("i", running_instance->recruit(name,*where->location_) == true ? 1 : 0);
)
}
PyObject* python_ai::wrapper_get_gamestatus(PyObject* /*self*/, PyObject* args)
{
if ( !PyArg_ParseTuple( args, "" ) )
if (!PyArg_ParseTuple(args, ""))
return NULL;
wesnoth_gamestatus* status;
status = (wesnoth_gamestatus*)PyObject_NEW(wesnoth_gamestatus, &wesnoth_gamestatus_type);
@ -1553,14 +1503,13 @@ PyObject* python_ai::wrapper_unit_attack_statistics(wesnoth_unit* self, PyObject
wesnoth_location* to;
int weapon = -1;
if ( !PyArg_ParseTuple( args, "O!O!|i", &wesnoth_location_type, &from,
&wesnoth_location_type, &to, &weapon) )
return NULL;
if (!running_instance->is_unit_valid(self->unit_))
return NULL;
if (!PyArg_ParseTuple(args, "O!O!|i", &wesnoth_location_type, &from,
&wesnoth_location_type, &to, &weapon))
return NULL;
if (!running_instance->is_unit_valid(self->unit_))
return_none;
if (weapon < -1 || weapon >= (int) self->unit_->attacks().size()){
set_error("Invalid weapon %d", weapon);
return NULL;
return_none;
}
info& inf = running_instance->get_info();
@ -1783,8 +1732,10 @@ void python_ai::play_turn()
python_code += "'" + *i + "ais', ";
}
python_code += "])\n"
"import wesnoth, safe, heapq, random\n"
"try:\n"
" execfile(\"" + script + "\")\n"
" code = file(\"" + script + "\").read()\n"
" safe.safe_exec(code, {\"wesnoth\" : wesnoth, \"heapq\" : heapq, \"random\" : random})\n"
"finally:\n"
" sys.path = backup\n";
PyObject *ret = PyRun_String(python_code.c_str(), Py_file_input,
@ -1794,7 +1745,8 @@ void python_ai::play_turn()
Py_DECREF(globals);
if (PyErr_Occurred()) {
// RuntimeError is the game-won exception, no need to print it
// RuntimeError is the game-won exception, no need to print it.
// Anything else likely is a mistake by the script author.
if (!PyErr_ExceptionMatches(PyExc_RuntimeError)) {
LOG_AI << "Python script has crashed.\n";
PyErr_Print();

View file

@ -58,7 +58,7 @@ public:
static void set_error(const char *fmt, ...);
static bool is_unit_valid(const unit* unit, bool do_set_error = true);
static bool is_unit_valid(const unit* unit);
std::vector<team>& get_teams() { return get_info().teams; }
static std::vector<std::string> get_available_scripts();
protected: