Vous êtes sur la page 1sur 19

Case Study: Embedding Python into

Counter-Strike: Source
Mattie Casper
March 1, 2008
PyCon 2008, Chicago

Abstract
The EventScripts plugin is the most popular scripting engine addon for Valve Software’s Source games,
running on more than eleven thousand servers globally as of January 2008. Its most recent version,
EventScripts 2.0, has added support for Python 2.5 scripts. ( http://python.eventscripts.com )

In this informal case study, I discuss the considerations of embedding Python into popular cross-
platform game servers. Areas covered include the need for a simple scripter interface for the gaming
audience, issues in cross-platform embedding, and methods for handling performance and stability
issues.

This study is of interest as an example of embedding Python in existing software packages. It is also
useful in understanding the limitations and difficulties associated with embedding the language runtime.

Table of Contents
Abstract .................................................................................................................................................. 1
Table of Contents .................................................................................................................................... 1
Introduction ............................................................................................................................................ 2
Background ............................................................................................................................................. 2
Multiplayer PC Gaming ........................................................................................................................ 2
Source-based Games ........................................................................................................................... 3
EventScripts Plugin .............................................................................................................................. 4
Goals and Results .................................................................................................................................... 6
Simplicity ............................................................................................................................................. 6
Platforms ............................................................................................................................................. 9
Performance...................................................................................................................................... 12
Security ............................................................................................................................................. 13
Multiplicity ........................................................................................................................................ 14
Future Possibilities ................................................................................................................................ 16
Recommendations for Improving Python............................................................................................... 17
Make cross-platform embedding easier ............................................................................................. 17
Support true threading ...................................................................................................................... 17

Page 1 of 19
Modernize API documentation .......................................................................................................... 17
Conclusion............................................................................................................................................. 18
Additional Information .......................................................................................................................... 18
References ............................................................................................................................................ 19

Introduction
This informal case study reflects upon the results of a development community’s success at embedding
Python into a plugin for Valve’s Source games. For readers unfamiliar with the project, an overview is
provided of multiplayer PC games, the Source engine, and EventScripts history. The primary content of
the paper describes the different goals and results in areas such as performance, stability, and platform
support. Finally, recommendations are made for continuing to expand upon EventScripts and also for
improving Python for the embedding community.

This project is primarily useful as an example of embedding CPython inside existing software packages. It
should also help show the limitations and difficulties associated with embedding the language runtime.

Background

Multiplayer PC Gaming

Multiplayer PC games are growing every year in popularity. One popular subset of these online games is
called “first person shooters” (FPS) that allow players to simulate contests around combat-oriented
objectives. The game players in this genre are historically very loyal; for example, the game Counter-
Strike from Valve Software was first introduced in 1999 and still has as many as 60,000 players at any
given time according to popular game-statistic websites (1).

Game Servers
Most first-person shooter games use player-hosted servers that connect players for their battles. These
servers typically host 16 to 64 players who find each other through centralized services like Valve’s
Steam software or GameSpy.

While games servers can often be run on one of the player’s machine alongside their game client, the
CPU intensive nature of these games can lead to performance issues. As such, players often host games
on dedicated game servers to mitigate any CPU contention. This practice has created a robust business
sector for game server rental providers. These companies configure servers and rent them, often on a
monthly basis, to players who have private or public gameplay needs.

Extensibility
Many of the most-popular player-hosted game engines are extensible, offering SDKs and plugins that
can extend the functionality of the servers. For example, games like Battlefield 2 use a scripting interface

Page 2 of 19
in Python to allow game administrators to customize their servers with new objectives and new
gameplay rules. Many other games allow for extensions written in C++.

Popular games that do not have a vendor-supplied extensibility model often find that one is provided for
them. For example, the original Counter-Strike game did not include an SDK, but community hackers
developed plugin hooks via a project called Metamod that allowed server-side customizations.

Source-based Games

Overview
Half-Life by Valve Software was one of the most popular first-person shooters of all time. The game’s
physics and graphics engines became a prolific basis for new content in the gaming community. Popular
games (or “mods”) like Counter-Strike .

After the success of the Half-Life engine, Valve began work on their “Source” game engine, which was
first released in 2004 with Half-Life 2 and Vampire: The Masquerade – Bloodlines. (Interestingly,
Bloodlines embedded its own Python interpreter to control some story elements, goals, and entity
interactions.) Counter-Strike: Source was released for the PC in late October 2004 and has grown an
audience nearly as large as its predecessor.

Source Server Plugin Model


The Source engine was released with the Source SDK – a development kit for the PC which provided
source code and tools to create mods for Source. In addition, it included a C++ interface for creating
server-side plugins for games written on the engine. This paper discusses one such plugin, EventScripts.

Server-side plugins for Source are driven primarily by callbacks and event notifications. These C++
projects implement the IServerPlugin interface and the engine uses that to deliver interface factories
and general callbacks as the game progresses. (2)

When the server starts, the engine enumerates the game’s addons folder and loads any plugin DLLs
from that directory. Each plugin is given a turn to use interface factories to access global services
provided by the Source engine. For example, the Source engine provides the following services accessed
through interfaces:

 Player information lookup


 Generic visual/sound effects
 Registering console commands
 Registering new and adjusting existing server variables (e.g. sv_gravity to adjust simulated
gravity)
 Physics engine interaction
 Basic entity manipulation
 Bot and player manipulation

Page 3 of 19
Once everything is loaded, the primary interactions happen through callbacks that the server invokes for
every plugin loaded:

 Generic game event notifications like “player_hurt”, “round_start”, “player_spawn”


 Server tick callbacks, to execute quick recurring code every game frame.
 Server console command callbacks that were registered by the plugin
 Notification and interception of client command messages (i.e. commands sent from players like
“buy ak47”)

EventScripts Plugin

Overview
EventScripts is a free script engine plugin which lets gamers create enhancements for Source game
servers. The plugin is currently the most popular scripting engine for Source games, with over eleven
thousand game servers running at any given time (3). EventScripts was originally developed as a shell-
like language for game server consoles with limited functionality. In recent years the project has evolved
to wrap most of the Source SDK plugin model and has added Python 2.5 language and library support.

EventScripts has always been delivered in a distribution package that includes everything needed to run
on Linux and Windows. Installation involves simply unzipping the package to the addons directory and
restarting the server.

EventScripts Classic Scripting


The EventScripts plugin for Counter-Strike: Source started as a simple way to allow game administrators
to run commands when game events occur. This utilized the game’s existing configuration file format for
changing server variables.

For example, some custom maps (game levels) would change the server gravity whenever a round
started. Administrators could use EventScripts with a file like the following to change it back:

round_start.cfg in cstrike/cfg/events
01 // reset gravity back to normal
02 sv_gravity 800
03

Over time, EventScripts v1.0 and later included more logic and additional file formats. It became a full-
fledged shell-like language (not unlike a batch file or shell script) which allowed more complicated
addon structures that could work side-by-side. The focus remained on creating a beginners language,
but some hidden complexity was added to satisfy dabbling experienced coders. As part of a successful
experiment, the script filenames were given a “.txt” file extension to encourage non-scripters to open
the files and get exposure to the simple syntax.

The same script above written in using the EventScripts 1.0 model would be:

Page 4 of 19
es_gravityfix.txt in cstrike/addons/eventscripts/gravityfix/
01 event round_start
02 {
03 // reset gravity back to normal
04 sv_gravity 800
05 }
06

The above would be loaded by entering es_load gravityfix in their server game console.

Given the side-by-side nature of addons, scripts could be isolated and released to the community. For
example, the following script could work side-by-side on a server with the above gravityfix loaded:

es_knifemsg.txt in cstrike/addons/eventscripts/knifemsg/
01 event player_hurt
02 {
03 // if attacker uses a knife, tell them how much damage it was
04 if (event_var(weapon) equalto ‚knife‛) do
05 {
06 es_tell event_var(attacker) You sliced for event_var(damage) points.
07 }
08 }
09
10 event player_death
11 {
12 if (event_var(weapon) equalto ‚knife‛) do
13 {
14 es_tell event_var(userid) You were killed with a knife. How depressing!
15 }
16 }

EventScripts Python Scripting


EventScripts 2.0 now provides support for scripts written in Python 2.5. The same knifemsg script from
earlier ported to Python might look like this:

knifemsg.py in cstrike/addons/eventscripts/knifemsg/
01 import es
02
03 def player_hurt(event):
04 # if attacker uses a knife, tell them how much damage it was
05 if event[‘weapon’] == ‚knife‛:
06 es.tell(event[‘attacker’], ‚You sliced for %s points‛ % event[‘damage’])
07
08 def player_death(event):
09 if event[‘weapon’] == ‚knife‛:
10 es.tell(event[‘userid’], ‚You were killed with a knife. How depressing!‛)

Page 5 of 19
Python support is linked and embedded inside the core Windows DLL and Linux shared object that
EventScripts uses for its host plugin. The plugin contains most of the low-level Python interaction and
many wrapper functions for the Source SDK.

Scripts typically interact with the Source engine through the es module, which is defined in es.py. This
module imports low-level functions from the es_C module and includes high-level functions and classes
to hide some of the underlying complexity from scripters.

EventScripts Community
It’s also important to understand a little about the community surrounding EventScripts. Founded with
the notion that even non-programmers should be able to customize their servers with simple scripts, the
community has grown tremendously. It is common to see increasingly complex script releases from
experimenters who have never coded in their lives. Gaming is a tremendously powerful and popular
entry point for budding developers, and EventScripts has offered a great place for people to get started.
Python is a natural transition language and the adoption has grown beyond expectations. Its power
coupled with its ease-of-use make Python a great stepping stone to formal programming from early
experimentation. When employers hire Python coders of the future, they shouldn’t be surprised to see
game scripting on their résumés.

It is difficult to measure the potential gaming has for growing the development community, but there’s
no doubt it is tremendous. Gamers can be very passionate and will overcome many technical barriers in
order to improve their gaming experience. Tapping into that passion is a great way to grow the world’s
supply of developer talent.

Goals and Results


There were a number of goals for this project and development time spanned many months. This
section provides an overview of those goals and then discusses the actual approach used to embed
CPython 2.5.1. In the end, Python did a fairly good job of handling the problems thrown its way, but it
wasn’t always easy to integrate.

Simplicity

Goal: Easy to read and learns


The classic shell-like language of EventScripts gained its popularity by being simple enough for non-
programmers to understand it. As such, the language needed to be gentle on beginners and readable by
just about anyone.

To encourage the existing user base to adopt the new scripting scheme, any added language needs to
either, (a) resemble the existing EventScripts language or (b) support enough dynamic adjustment that it
can be structured in a form readable by users of the previous language.

Page 6 of 19
A language with complex features is acceptable as long as those are not required for everyday use or
can be abstracted and hidden via libraries.

Results: It’s not easy being easy

Simple syntax
Python is touted as a great beginner’s programming language (4). For example, it has a starring role in
One Laptop Per Child environment (5) designed for educational uses. Overall, Python’s syntax was a
good fit because it’s easy to explain to new scripters and there are many published tutorials to
reference.

Hiding Complexity
The initial prototype design for the Python version of EventScripts required addons to be classes which
registered callbacks for the events they wanted to observe. For example:

hello.py (early prototype format)


01 import es
02
03 class HelloAddon(Addon):
04 def load(self):
05 es.addons.registerForEvent(self, 'player_spawn', self.player_spawn)
06
07 def player_spawn(self, event_var):
08 es.tell(event_var['userid'], "Hello! Welcome to the server.")
09
10 hello = HelloAddon()

This approach makes sense to developers familiar with the observer design pattern, but is likely too
complex for new scripters. To simplify the model, EventScripts now handles the event registration
automatically and doesn’t require classes or object-oriented techniques by default. Under the official
EventScripts 2.0 addon structure, the following addon would be identical to the one listed above:

01 import es
02
03 def player_spawn(event_var):
04 es.tell(event_var['userid'], "Hello! Welcome to the server.")

The convention of using event_var as the parameter name for events is a throwback to the classic
EventScripts language and, just like Python’s self argument for class methods, is optional but widely
used for consistency. It was chosen to encourage readability for former users of the classic EventScripts
language. A similar construct was created for server_var for referencing server variables similar to the
method used in classic EventScripts.

Page 7 of 19
Python addons are loaded as modules and, after import, EventScripts adds all functions from that
module into a dictionary of event listeners (unless they begin with an underscore, as per the convention
for private methods). The following snippet from es.py shows how this is done:

01 for f in newaddon.__dict__:
02 # loop through every function and register them as blocks
03 if type(newaddon.__dict__[f]).__name__ == "function":
04 # Ignore 'private' blocks as denoted by an underscore prefix
05 if f[0] != "_":
06 # right now all functions are treated as both blocks and events
07 addons.registerBlock(modulename, f, newaddon.__dict__[f])
08 addons.registerForEvent(newaddon, f, newaddon.__dict__[f])

This technique could be enhanced to register blocks and events based on the parameter count of the
functions, but it doesn’t currently do so. (A “block” here is an ES legacy concept of a subroutine with no
arguments.)

Once the game has started, whenever EventScripts is notified of an event by a certain name, it looks for
that name in the event listeners collection. It then calls any functions keyed for that event:

01 def triggerEvent(self, eventname):


02 if not self.EventListeners.has_key(eventname):
03 return
04 for listener in self.EventListeners[eventname]:
05 if not listener.__dict__.has_key('disable') or not listener.disabled:
06 try:
07 self.EventListeners[eventname][listener](event_var)
08 except:
09 # output the exception information, but keep going
10 excepter(*sys.exc_info())

This automatic registration of event methods allows beginners to use a simpler addon format, while
advanced scripters can still register their own individual callbacks if they understand the necessary
concepts.

Porting existing commands


To further simplify the transition to Python, the es module includes functions similar to their old es_*
counterparts from the original language. For example, legacy commands like es_msg,
es_createplayerlist, and es_changeteam were wrapped and imported into the es module to produce
es.msg(), es.createplayerlist(), and es.changeteam().

The C++ abstract classes for Valve’s interface management and the macro-heavy nature of the existing
code made it difficult to use a tool like SWIG1 for porting these existing commands. The mirroring wasn’t
difficult to accomplish manually, but it required even more convoluted preprocessor macros to allow
existing console commands to support invocation from Python. Given that there were over 150

1
See http://www.swig.org/ for more information on the great SWIG tool for making language extensions.

Page 8 of 19
commands to mirror, development expediency through complex macros took precedence over an ideal
porting structure. As one example, since Source console commands inherently have their arguments
passed to them as strings (much as command-line executables would via argv, argc, etc.), the macros
would call functions that would allow all parameters passed from Python to be read as strings. The
general process for “retokenizing” Python arguments follows this algorithm:

 Each command wrapper was registered using the METH_VARARGS flag2


 When a command is called, the plugin:
o Loops through all arguments via PyTuple_GetItem()
o Converts each item to a C character array
o Uses those strings as the simulated argv arguments
o Combines those strings to recreate a simulated args (or full command string)

While this is inefficient in a few ways, it allowed for quickly integrating Python support that works much
like classic EventScripts commands. Ideally each method would extract the exact type it needed from
Python (e.g. an int for a userid) and wouldn’t require a combined command string, but the current
method has proven sufficient.

Many other tips and tricks were used to further simplify usage. For example, numerous libraries were
added to provide functionality similar to the original EventScripts but in a Python-friendly way. Modules
like playerlib, votelib, and keyvalue continue to evolve to provide easier access to complex methods. The
below example shows a script using playerlib to cause players to launch into the air when they’re shot:

popcorn.py in cstrike/addons/eventscripts/popcorn/
01 import es
02 import playerlib
03
04 def player_hurt(event_var):
05 popstar = playerlib.getPlayer(event_var['userid'])
06 popstar.push(vertical=1000,vertoverride=True)

Platforms

Goal: Cross-compile for Windows and Linux


Source game servers run on both Windows and Linux. As such, it is important for EventScripts to support
both platforms, too. While it’s common for rental companies to host games on Linux variants, script
development typically happens on Windows since the graphical game clients run there.

To complicate matters, it’s important to note that the installed platform is commonly a managed game
server rental. This makes it difficult or impossible for users to install prerequisites or tweak system
services. The environment is commonly controlled through a web-based control panel or through the
game itself, because renters are not given direct shell access to their servers.

2
See http://docs.python.org/ext/methodTable.html for more information on METH_VARARGS

Page 9 of 19
Due to this arrangement, it’s common for renters of game servers to be unaware of the specific
operating system running their game server. To avoid confusion, EventScripts provides all Linux and
Windows binaries in its primary download package.

Results: Library embedding is messy


Resolving platform issues was probably the most problematic part of embedding Python. While CPython
supported compilation on every Linux variant and Windows variant that needed to be supported, it was
clear that Python wasn’t designed to be embedded easily in apps that cross-compile for Windows and
Linux.

It’s possible that a number of these issues have solutions documented somewhere, but a great deal of
research and experimentation did not uncover solutions easier than those chosen. A number of
problems likely arise because of the use of a similar directory structure on Windows and Linux, which
may be a difficult goal for cross-platform applications. That being said, this is the model that Valve uses
for the Source dedicated server—the plugins need to follow this approach to properly support the
platforms.

Library loading issues


Creating an install zip file that would support xcopy deployment for EventScripts was nearly an
unsolvable problem. This is due to the fact that embedding Python 2.5, as far as research indicated,
required linking against a python25.dll (Windows) and libpython2.5.so.0.1 (Linux). Adding this
dependency may not be a problem for a full-fledged hosting executable that may be able to control
more aspects of its environment, but embedding Python into a dynamically-loaded library was
problematic.

The Source engine loads addons from the addons folder dynamically via the server host executable
srcds. As such, when EventScripts was loaded, it could not find the library path in the loader path.
Unfortunately, the Python libraries couldn’t be placed in the required directory because the current
working directory for this process is a subdirectory typically not accessible in FTP access for game server
rentals3.

On Windows this problem was resolved by linking python25.dll as a delay loading4 DLL. This allowed the
following sequence:

1. EventScripts plugin is loaded by srcds.


2. Before Python initialization, LoadLibrary() is called on the python25.dll from the appropriate
subdirectory.
3. During Python initialization, the delay load management loads python25.dll, but because it’s
already loaded, the process is given the same module handle it already held, allowing functions
to be called normally.

3
The srcds/bin directory is protected to avoid renters circumventing the player and tickrate limits they paid for.
4
Information on delay-loading DLLs on Microsoft’s site: http://msdn2.microsoft.com/en-us/library/151kt790.aspx

Page 10 of 19
On Linux, unfortunately, research did not reveal a means of reliably late binding python2.5.so.1.0 in a
manner that would allow the same solution as Windows. Attempting to statically link libpython.a into
the Linux EventScripts plugin didn’t work properly when loaded. Even after following instructions to add
linker options as defined in the Python documentation (6), the proper symbols were not exported from
the EventScripts shared object for access by loaded modules. This is possibly because srcds does not
load the plugin with the proper options to export Python symbols globally.

Python embedding was nearly dropped until the discovery of the limited documentation for using the --
rpath linker option on Linux. This option allows an executable to specify a path to look for its
dependencies and the Linux library loader respects these. After a lot of trial and error, this proved a
sufficient solution for most cases.

The delay load and rpath solutions allowed the project to continue, though not without some further
issues discussed later.

Python path issues


In preparing EventScripts on Linux, there were a handful of situations involving the Python path that
caused problems.

For example, on Linux, Python appends a “/lib” suffix to paths specified as PYTHONHOME even when set
solely through Py_SetPythonHome(). Since all of the modules packaged with EventScripts were installed
into platform-neutral directories to be reused on Windows and Linux, a /lib suffix was not used. To avoid
this issue, the Python source for Linux was modified to exclude this suffix. It’s possible that registering
an import hook to import modules and avoiding PYTHONHOME may have resolved this issue. Either
way, it requires extra work to prepare the initial Python environment for an embedded application to
use the application’s subdirectory.

Compilation issues
On the Linux build system for EventScripts, when running ./configure for Python compiles, it wasn’t clear
initially that it was skipping compilation of some modules. A number of important modules from the
standard Python distribution (e.g. zlib and sqlite3) were skipped during compilation without an obvious
indicator that these were excluded. This issue wasn’t discovered until in-depth testing of the Linux
packages revealed the absences. After tracking down the prerequisites and performing a new
./configure, these modules were built successfully.

Directory names with colons


One final platform-specific problem that arose was due to various Linux confusions when colons (“:”) are
used in directory names. This is troublesome for rented game servers because major rental companies
include a colon in the directory names for their users, naming them according to the IP:PORT assigned to
their game server (e.g. myserver/192.122.1.2:25015/srcds). One of the largest game rental companies
uses this directory naming convention on Linux and it causes many users to have substantial problems
using EventScripts with Python.

Page 11 of 19
It’s likely that paths containing colons are not overly common given its use as a Linux path separator
character, yet colons are an allowed character for directory names. As it turns out, Python and the Linux
library loader are easily confused by this.

One area where this causes problems is when using the --rpath linker option to specify a search path for
libpython2.5.so.1.0. The Linux library loader could not replace $ORIGIN in the RPATH if quotes were
used on the path, and saw any colon in the path as a separated list of paths to look for. As a result,
EventScripts cannot load the Python shared object and fails to load. This issue still has no satisfactory
workaround and the affected users must submit technical support requests to the company to copy the
Python shared object into protected directories.

Even when the RPATH issue is resolved, there are still some key bugs inside Python that are caused
when a colon is on the path. If this exists in PYTHONHOME, then the sys.path gets split upon every
colon, seemingly because sys.prefix is being incorrectly split when being added to sys.path. This problem
is only worked around by explicitly performing a manual fix of sys.path to correct the appropriate
entries. A bug has been filed in the Python bug tracker for this issue5.

Once the above two colon-related issues are fixed, it seems some other modules are also confused by
the colon. For example, the codecs module uses a search path that path breaks when a colon exists in
the parent directory structure. So far a workaround for this issue has not been found, but it would likely
require a patch to the codecs module.

On a positive note, there were far fewer issues with different flavors of Linux than expected given that
EventScripts is not recompiled for each edition. It would never be viable to recompile anything on these
game servers during installation (because the server renter does not have access), so it’s quite fortunate
that there are so few issues with the Fedora Core-based compile running on other Linux flavors.

All in all, the work to embed Python using a similar directory structure on Windows and Linux was
surprisingly problematic and is still unreliable. The blame for these complications lies in multiple places
(if not in the author’s lap entirely), so Python is not the sole cause for distress in these scenarios.

Performance

Goals: Need for speed


Performance is very important for game servers. The frame rate, network latency, and calculation
accuracy can all be affected when the server cannot respond in a timely fashion. The default tickrate for
Valve’s Counter-Strike: Source game is 33, which puts a game tick interval (or game calculation and
response interval) at about 30ms. Many game servers, especially competition servers, run at 100
tickrate or 10ms intervals.

5
Issue number 1507224, http://bugs.python.org/issue1507224

Page 12 of 19
These short timeframes leave very little room for any computations or even moderately expensive
loops. Delays that reduce the number of ticks per second can often be felt by game players as ‘lag’ or
near-misses caused by lag compensation.

As such, the performance footprint of the EventScripts scripting engine needs to avoid impacting the
perception of delays or lag in the game

Results: Quicker than expected


The original EventScripts language was not intended for high performance situations. It is entirely run-
time interpreted, much like a shell script. To the surprise of some members of the gaming community,
the language was quite popular despite no bytecode or native compilation.

Having proven that even a slow shell language can be tweaked to provide reliable performance for game
scripting, it was unlikely that Python’s speed would be a problem. This turned out to be true-- the speed
of Python’s bytecode interpreter is respectable and rarely adds noticeable lag for any moderately well-
designed script.

In order to help with optimization, EventScripts includes the popular Psyco module6 for just-in-time (JIT)
compilation of Python bytecode. In many benchmarks code, it showed a measurable improvement on
both Windows and Linux, though some light event-based scripts did not see any benefit.

Since adding Python, it’s clear that the community is pleased with the performance. Feedback has been
entirely positive and over two hundred addons have been created or adapted to use Python exclusively.

Security

Goal: Don’t feed the hackers


Avoiding the introduction of security exploits is very important for EventScripts. While game servers are
not extremely valuable as targets for commercially-motivated attacks, they are under constant and
heavy attacks from “griefers” and malicious gamers. These individuals face almost no repercussions for
attacking game servers, and, for amusement, frequently try to find ways to take control or deny access
to game servers.

Access to the server console is frequently only secured by a single password (i.e. rcon_password). If this
is compromised, the hacker can cause annoying game problems for the currently running instance of the
server, but Valve has taken care to make it difficult for the system or files itself to be harmed via the
console. This layer of protection should be maintained.

Results: Safety over convenience


For security reasons, the original EventScripts was incredibly limited as to what it could do on the host
operating system. Any data serialization was limited purely to carefully-named files with specific

6
More information on Psyco can be found at http://psyco.sourceforge.net

Page 13 of 19
extensions to avoid the possibility that a compromised server console could harm the file system or
operating environment of the game server.

Python currently doesn’t offer any secure or sandboxed mode that restricts access to the system. On the
contrary, it’s a very potent tool when it comes to manipulating system resources. If an attacker were
able to inject or invoke arbitrary Python code, it could be painful for a server rental company that did
not properly use operating system permissions to protect things. Even if they did restrict file access
properly, it would be difficult to secure assets the game server normally has access to, like the CPU and
networking.

To help reduce the possibility of damage, there is no default way to invoke raw Python code from the
game’s server console. This is really unfortunate, because the game server console makes an excellent
interactive interface to the Python interpreter. To allow developers the ability to access this while
coding, the console command pycmd_register <secret-name> will create a secret console command, but
only if no game map has been loaded. This requires the command to be registered before the server
starts and it cannot be invoked once the server is running.

In addition to limiting access to the Python interpreter, script addon authors are heavily discouraged
from using highly dynamic commands like exec() and eval(). If scripts using commands like this are
submitted to the addon database7, they cannot be marked as ‘approved’ for general consumption. The
intent here is to discourage authors from inadvertently introducing Python code injection vulnerabilities.

Unfortunately, despite great care, the release of EventScripts that first integrated Python did cause a
crash exploit (denial of service), but not due to any direct issue caused by Python. In providing access to
chat messages from a player (i.e. sayFilters), empty messages caused string functions to crash before
converting to Python objects. A quick update was released, but as of this writing, two hundred servers
remain vulnerable globally.

Multiplicity

Goal: Addons living in harmony


EventScripts allows loading multiple script addons simultaneously. Any new model or engine must allow
for multiple simultaneous scripts to be loaded and operating independently.

Results: Manager allows partial isolation


EventScripts and Source are structured heavily around events and callbacks for their addon models. As
such, an addon merely needs to be notified upon need, and coexists nicely in the same thread. Addons
only very rarely need to do calculations or have their own “main” body that would require their own
threads. As such, the primary concern was simply how to best load multiple Python (and EventScripts

7
EventScripts addon repository can be found at http://addons.eventscripts.com.

Page 14 of 19
classic) addons independently. A number of approaches were considered within Python to accomplish
this.

Loading addons
For example, early prototypes began using execfile() into the global space classes and registering an
instance of each with an addon manager. This model was workable, but it didn’t seem appropriate to
pollute the global namespace.

The next approach was to treat each addon as a module. This technique was appealing since it allowed
addons to avoid namespace collisions. This led to another problem with directory pollution since many
addons include resources (like localized language strings and intermediate files). To avoid this problem,
the original EventScripts language required a specific file and directory format, such as:
addons/eventscripts/myscript/es_myscript.txt. In this way, each addon had its own subdirectory to avoid
any conflict with files from another addon. To simulate this in a Python module-based method,
EventScripts added every loaded script subdirectory to the system path before it imported them.

The method that is currently used in EventScripts leverages Python package support for importing and
only adding the main addon root directory to the path. This allows using an addon file structure like
addons/eventscripts/myscript/myscript.py. Since Python packages require an __init__.py to be
recognized as a package, EventScripts will automatically create a 0-byte file for that before loading the
addon. Since EventScripts also supports the notion of loading “subscripts” of an addon (e.g.
addons/eventscripts/myscript/subscript/subscript.py, this worked naturally via package support as long
as __init__.py files were created along the directory path.

In hindsight, it would have served the needs of EventScripts to create a custom import hook (7) to
provide more natural behaviors for this environment without needing to exploit the Python package
management.

Isolating exceptions
One other important consideration for multiple simultaneous addons is to protect each script from the
exceptions of others. This required vigilance while creating notification handlers such that addon
exceptions were reported but other listeners were given a chance to continue. The machinery in the
EventScripts addon management also needed robust exception handling to avoid having addon
exceptions from interfering with the core management functions. This consisted primarily of open-
ended try/except blocks and a global exception hook8 to manage error reporting.

Threading
One major problem for Python embedded applications is the poor support for asynchronous threading
due to the Global Interpreter Lock (GIL). While it’s true a lot of the C++ game processing can be handled
asynchronously from Python and that scripts can achieve execution isolation via threading, all Python
addons share the same GIL-based synchronization system and cannot get solid thread performance for

8
Exception hooking uses sys.excepthook as discussed here: http://docs.python.org/lib/module-sys.html

Page 15 of 19
Python-related processing. Unfortunately, the oft-quoted responses to Python’s poor threading support
aren’t useful for applications that embed the interpreter.

For example, it’s a common argument that Python developers should use multiple processes instead of
threads to get an asynchronous model. This may be somewhat reasonable for stand-alone scripts, but
applications embedding Python typically have a very different focus than pure scripting and cannot
necessarily be forked or respawned in their entirety on demand. It’s also not very reasonable to ask
those applications to ship an independent and stand-alone “Python lite” interpreter executable. In the
case of EventScripts, it cannot redistribute host executables (e.g. exe’s or processes to be spawned)
because those often cannot be deployed in the rental server environment.

Another common argument is for developers to create a Python C extension to manage asynchronous
bits and release the interpreter lock during periods of active calculation. This is also an unreasonable
expectation for embedders like EventScripts whose audience may only be casual scripters and not C/C++
developers or expert Python coders. In other words, casual threading in a high-performance application
is problematic who have not learned C programming.

The fundamental deficiencies in this category are the biggest issues with embedding Python over
another language. In this project, the other benefits of CPython outweighed this issue, but if another
Python implementation presents itself as a viable embeddable interpreter, we would consider it
because of the doors it opens for powerful asynchronous tools such as game bot intelligence.

Future Possibilities
Now that EventScripts includes Python support, the possibilities are nearly endless. Exciting new
features are in prototype stages, and others are already in private beta testing.

Some possibilities include:

 es_install – This is a feature to allow servers to download and install EventScripts addons in a
similar manner to EasyInstall9 from the setuptools module. This is expected to be a popular tool,
but care is required to avoid security implications (i.e. an attacker gaining console control and
downloading malicious scripts).
 EventScripts classic in Python bytecode - Create a Python-based parser for the original
EventScripts shell language that compiles those scripts to Python bytecode.
 Lower level Source SDK wrapper – Instead of exposing primarily the existing EventScripts
libraries to Python scripts, a lower level Source SDK library could be exposed to offer even more
power and cover areas EventScripts hasn’t exposed previously. One possibility here is to work
with tools like SWIG incrementally over important classes and interfaces.
 Auto-update mechanism – This initiative will allow EventScripts itself to be quickly updated in
the event of a security exploit.

9
More information on easy_install: http://peak.telecommunity.com/DevCenter/EasyInstall

Page 16 of 19
Without Python, a lot of these proposals would be too difficult to code in a reasonable, hobbyist
timeframe, so the future looks very promising.

Recommendations for Improving Python

Make cross-platform embedding easier

The support for embedding the interpreter into applications is an advanced process and is treated
almost as an afterthought. This has improved incrementally across releases, but still does not feel like a
priority for the Python community. In a world where we see entire industries grow out of the ease in
which other technologies embed into one another, it’s a shame that the Python community neglects the
possible growth by focusing more on a solid embedding experience.

Currently, only an experienced developer can embed CPython in real world applications and it takes
considerable time to do so. If more efforts were made to improve documentation, tutorials, examples,
libraries, tools, and API, Python would offer a complete solution to readily support the exploding
number of devices and applications.

Support true threading

Embedded applications really cannot take advantage of alternatives to having proper threading support
in Python. As such, the current state of threading is just barely acceptable for embedders. As most
everyone in the Python community knows, the problem is not an easy one to solve. Yet, it is incredibly
important in an age of rapidly increasing processor parallelization and it will continue to slow Python’s
growth in popularity.

Modernize API documentation

When development on this project began, the Python documentation seemed decent and usable for
beginners. An experienced developer has no problem navigating the documentation just as they
wouldn’t have difficulty looking up a man page. Yet, when you widen Python to younger audiences as
EventScripts does, it becomes clear that the core Python API documentation is painfully out-dated in
terms of usability and reference usefulness. The help mechanisms inside the interpreter are quite
wonderful, but that is really not enough to provide new scripters the documentation tools they need.

Some suggestions to improve the comprehensive online documentation would be:

 Improve hyperlinking between related methods, modules, and classes


 Provide single, full pages for individual class methods and modules, including examples.
 Provide more information about return values, notes, and examples.
 Allow moderated user comments on the documentation pages to share common tricks,
deficiencies, and problems.

Page 17 of 19
 Allow users to rate the helpfulness of documentation and make documentation feedback
requests
 Provide on the fly searching or tree-based table of contents
 Allow users or the community to add tags to API descriptions or build their own relationships to
other

These may appear to be trivial and difficult to change (perhaps due to how the official Python
documentation is produced), but it’s absolutely clear after working with new scripters that the current
state of documentation hurts adoption by those trying Python for the first time.

Conclusion
Python was successfully integrated into the Source engine via the EventScripts plugin, bringing many
new scripting possibilities to games like Half-Life 2 and Counter-Strike: Source. The integration was able
to meet the intended goals of: encouraging beginner scripters, deploying on both Windows and Linux
systems, working properly in a managed server environment, and performing well under intense game
processing.

There are a few key areas to improve Python for embedded application and game adoption. Enhancing
documentation, both for traditional APIs and for embedding techniques, would increase the popularity
of the language with application developers. In addition, removing or improving the Global Interpreter
Lock would empower embedding applications which otherwise have few alternatives for properly
parallelizing scripts.

Based on this case study, it’s apparent that Python is underutilized in game development. The power,
performance, and embedding capabilities would be a boon for any large-scale game. A big part of the
undeniable success of Valve’s Half-Life games is due to the extensibility and adaptability of their
platform. While they focus primarily on a C++ SDK, Python’s capabilities and ease of use make it an
excellent candidate to grow to an even larger audience of content creators.

Additional Information

 EventScripts homepage, http://www.eventscripts.com


 Python EventScripts documentation, http://python.eventscripts.com
 Community created addons for EventScripts, http://addons.eventscripts.com
 Download page for EventScripts, http://mattie.info/cs

Page 18 of 19
References
1. Game-Monitor.com. Servers Playing Counter-Strike. Game Monitor. [Online] [Cited: March 01, 2008.]
http://www.game-monitor.com/search.php?game=cstrike.

2. Valve Software. Server Plugins - Valve Developer Community. Valve Developer Community. [Online]
[Cited: February 1, 2008.] http://developer.valvesoftware.com/wiki/Server_Plugins.

3. Game-Monitor.com. Servers Playing All Games (with EventScripts). Game Monitor. [Online] [Cited:
March 1, 2008.] http://www.game-monitor.com/search.php?search=eventscripts_ver&type=variable.

4. Jordan, Patrick. A Very Quick Comparison of Popular Languages for Teaching Computer Programming.
Ariel Computing. [Online] [Cited: 24 February, 2008.] http://www.ariel.com.au/a/teaching-
programming.html.

5. OLPC Foundation. OLPC Python Environment. [Online] OLPC Foundation. [Cited: February 24, 2008.]
http://wiki.laptop.org/go/OLPC_Python_Environment.

6. Python Software Foundation. Linking Requirements - Extending and Embedding the Python
Interpreter. Python Documentation. [Online] [Cited: February 22, 2008.]
http://docs.python.org/ext/link-reqs.html.

7. Just van Rossum, Paul Moore. PEP 302 -- New Import Hooks . Python.org. [Online] Dec 19, 2002.
http://www.python.org/dev/peps/pep-0302/.

Page 19 of 19

Vous aimerez peut-être aussi