Code Snippets
Code Samples

The Blog
Our Projects

::Add RageStorm to Favorites!::

The Blog | Our Projects | Guest Book | About | Contact

Tutorial - Embedded Python
Uploaded at:29-May-03

Instead of writing your own l33t scripting engine, you could simply embed Python into your application. Python becomes a popular scripting language with time and I think it's a great language anyways. It's really easy to program in Python, your code is very readable and flexible. The Python's moto is "Batteries Included", which means that everything comes bundled already. Rarely will you have to download modules from the Internet and install them. I chose this subject, Embedded Python, as a solution to solve this mess that you have with huge application that let the end user write some scripts. Developers' efforts should be on their own application's concept mainly and not on that (small) scripting engine. (Unless your target is to write a scripting engine yourself, goodluck anyways!). Python is there, and is waiting for you, so why not using it?

In this tutorial I'm going to teach you how to use the Python C API. I'm writing this tutorial as I saw that there are merely a few resources on the Internet which will show you how this thing goes on. Unfortunately, the Python's C API documentation could be written much better. After I smashed my head into the wall and learned some stuff that could aid you, I decided to come up with a tutorial which will show how to use it in the right way, and tell you some tips and tricks.

The main topics I am going to cover:

And finally, setting up your own scripting console.

Well, I hope it's not too late to say that this tutorial assumes you know how to program in Python. Another thing, this is not a Python Reference, you'll still need the Python Documentation like hell!

It's everything about Objects:

Every object has its own reference counter. This means that if you define an int:

x = 5

The int object "X" will have 1 reference(s), so its reference counter will be 1. Now, when you define another int that will be assigned to "x":

y = x

What happens is that we created another int object, "y", but now it will point(!) to the "x" int object, thus making the "x" object's reference counter being increased by 1, so it would be 2 ("X" itself and "y").

One of the advantages of using this reference counting mechanism is for saving memory space. To be honest when using integers it doesn't really matter, but when it comes to strings and other blocks of data it's important.

Reference counting helps Python to "know" when to free objects (When an object has no references), so the used memory is available again. This will make us a big headache later, but don't worry.

Now let's say we delete "y":

del y

What happens behind the scenes is that the reference counter of int ojbect "x" is being decreased by 1. And still the int object "x" is alive. If we delete "x":

del x

Then the reference counter of "x" will become zero(0) and "x" is freed, because none needs it. When we'll mess with Python objects in C it will be all about reference counting (almost), so pay attention to it.

Let's define a Python integer object in C:

PyObject* pyIntObject = NULL;
pyIntObject = PyInt_FromLong(5);

Note: Almost all Python types/objects are interpreted in C as PyObject structure, and this is the only one we are going to use in here. This is equivalent to: x = 5 in Python. Which means the function PyInt_FromLong increased the object's count reference to 1 upon creating it. So when we want to get rid off pyIntOjbect we will have to decrease its count reference by using Py_DECREF(pyIntObject);

Py_INCREF and Py_DECREF to Our Call:

In the documentation of Python C API you'll see that every function says if it returns a NEW REFERENCE or a BORROWED REFERENCE. You should follow it correctly or otherwise memory leaks will occur and other big no-no things.

When you create an object, say with PyInt_FromLong, this function will return a new reference, means we have to delete it on OUR OWN! On the contrast when you GET(borrow) an object, usually, it means you must not delete it by all means!

So after calling no matter what Python C API function (which returns an object) make sure if you need to use Py_DECREF on it or not. When to use Py_INCREF, say you got(again - borrowed) an object (doesn't matter of which type now) from the user and you want to use it then you'll have to increase its reference count. Of course not forgetting to Py_DECREF it laters on. We'll see an example later.

It's really important to understand the organization of Python objects, otherwise you will use it the wrong way and you really don't want to, so take your time. :)

My first Embedded Python application:

If you didn't download Python yet, this is the time to visit their site: Python.org. Now, after you've installed it, make sure you know where the "Python.h" and "pythonXX.lib"(XX - version of installed Python) files are! Alright, let's start, all we have to do is to initialize the Python interpreter which will execute our Python commands for us:

// Don't forget to import "pythonXX.lib"!
#include <Python.h>
int main()

 // TODO: Add your Python code here.
 PyObject* pyIntObject = PyInt_FromLong(5);
 if (pyIntObject == NULL) ; // Error


 return 0;

That was it, but, hey, it's a full embedded Python application, which does nothing special yet.

Running some strings:

How we make things alive here?
You feel you still don't see what the whole magic is about?
There are a few functions which let you run strings as if they were the input of the end user. We'll use one of them to simulate some function calls: create a directory named "tmp" and immediately deleting it. Duh.
Let's dance:

// Don't forget to import "pythonXX.lib"!
#include <Python.h>
int main()

 PyRun_SimpleString("import os\n"
			"from os import mkdir, rmdir\n"
			"print \"We create dirs-we delete dirs\"\n");
 return 0;

Notice we suffix every single Python command with "\n" (LF) character, otherwise Python won't execute it. After executing this program, you'll see, if you are quick enough, that a directory named "tmp" was created and then just deleted.

Extension Modules:

As you know those .py or .pyd files, which are usually modules or classes, you name it, which supply a set of functions or/and objects. You can do the same but dynamically, without the need of an external imported .py or .pyd files. "Dynamically" means that all the functions and objects you supply will be written in C (Yes, you can use PyRun functions as well). So let's say we want to have a module named "MyFirstModule" and will supply two functions named: "Foo" and "Bar" of course. It will look like this:

PyObject* MyFirstModule_Foo(PyObject* pSelf, PyObject* pArgs)
 // You can run any C code in here.
 MessageBox(NULL, "Foo was called!", "MyFirstModule", MB_OK);

As you see a C function which can be called from Python has to return a PyObject*, get 2 arguments(which you can ignore), but usually you'll ignore the first one (It should point to the class object if the function is in a class, just like a method in C++). The second value is a pointer to a Tuple object of the arguments the function should process somehow.

PyObject* MyFirstModule_Bar(PyObject* pSelf, PyObject* pArgs)

static PyMethodDef MyFirstModulebMethods[] = {

    // "Python name", C Ffunction Code, Argument Flags, __doc__ description
    {"Foo", MyFirstModule_Foo, METH_NOARGS, "A dummy function."},
    {"Bar", MyFirstModule_Bar, METH_VARARGS, "An advanced dummy function which prints its argument."},
    {NULL, NULL, 0, NULL}

So we defined two functions named: "Foo" (which doesn't get any arguments) and "Bar (which gets arguments), and both contain some description.

First argument is the name of our module to Python code itself. The second argument is the array of functions we "export" to Python.

// Ending up with calling, so users can use it and import MyFirstModule.
Py_InitModule("MyFirstModule", MyFirstModulebMethods);

The Py_InitModule function makes our C functions available to Python code. We could run now something like:

import MyFirstModule
from MyFirstModule import *
And we'll get the MessageBox popped up on our window. But what about the other function, "Bar"? Read on.

Building and Parsing Arguments:

There are two functions which will really help you when you get arguments(parameters) when your function is called in C from Python. When a C function is called, you will have to parse the arguments and CONVERT them into C variables. There is a simple function which does the work, almost automatically, for us, "PyArg_ParseTuple". As you know Python functions' arguments list is actually a Tuple object.

So in "MyFirstModule_Bar" we defined "pArgs" which is a Python generic object, in our case it would be a Tuple containing all parameters from whom called us. Let's say we want to get two parameters from the caller, a string and an integer, the "PyArg_ParseTuple" works much like "printf" but instead of printing the variables it converts them into C type variables, thus we can use them in our functions as it were called directly from another C function.

In our case to convert the Tuple arguments into a string and an integer we would do:

char* str = NULL;
int num = NULL;
PyArg_ParseTuple(pArgs, "si", &str, &num);

Reminds scanf isn't it?
Now we have a read-only string pointed by str and an integer number. Make sure you never Py_DECREF/free/delete/write to the objects you get from "PyArg_ParseTuple" and the other parsers! Except that, with the C variables(which are a copy, like the integer), you can do whatever you are up to.

What about returning value(s) when my function is finished? Therefore there is a function, the inverse(take or give...) of "PyArg_ParseTuple", "Py_BuildValue". Noticed we didn't return anything in "MyFirstModule_Foo", I guess your compiler was yelling at you already about that...

So now we can do: return Py_BuildValue("s", "This string is returned!");

It should be reasonable to you that we just created an object, so who will free it? Of course Python will do the job when none uses it already. Read the Python Documentation about these two functions, so you see what other types are supported, how to use it further and error handling.

The None Object:

I just had to tell you that you can't just return NULL;(We'll get to this specific later in Exceptions) or any other C variable you want when you have to return a PyObject*.

We have two options: To return an object we built.
Or to return None.
The C equivalent to Python's None is Py_None. It's a "pre-defined" object which does nothing special, heh.

If you plainly return Py_None, and none will reference to it, you are letting it free, which is a wrong move. What you have to do a second before you return it, is increasing its reference counter, by using:

Py_INCREF(Py_None); // And just then:
return Py_None;

Now, after we know pretty good, what's going on, our functions will look as such:

PyObject* MyFirstModule_Foo(PyObject* pSelf, PyObject* pArgs)
 // You can run any C code in here.
 MessageBox(NULL, "Foo was called!", "MyFirstModule", MB_OK);

 return Py_None;


PyObject* MyFirstModule_Bar(PyObject* pSelf, PyObject* pArgs)
 char* str = NULL;
 int num = NULL;
 PyArg_ParseTuple(pArgs, "si", &str, &num);
 // Do anything you wish with the integer and the string...

 // Return a string:
 return Py_BuildValue("s", "This string is returned!");

Voilà! We have some fresh meat, and can start working with Python embedded in our application! But let's rule the world! So keep on reading...


One of the problems with returning NULL is that Python interprets this as an indication that an exception was raised. That's why we were returning Py_None. Let's get back for a minute to the "PyArg_ParseTuple" function. We "tell" it to parse a string and an integer(yeah, the old example is still valid). Now what if the user has called our function supplying only an integer, or supplying only a string, or not supplying any arguments at all, or everything but what we want. Then "PyArg_ParseTuple" will return 0 and raise an exception. Returning NULL will let the user know what was wrong... Check it out yourself, if you don't believe me.

My code looks like this: if (!PyArg_ParseTuple("...)) return NULL; And the rest will happen automatically by Python.

Or you can do it on your own with setting the Standard Exception:

if (!PyArg_ParseTuple("...)) {
 PyErr_SetString(PyExc_StandardError, "Your own error message!");
 return NULL;

Logging Stdout:

It's time to SEE something on the screen, don't you think so? In Win32 Console application the output is alright by default. However, what we were missing is a way to tell the Python interpreter to print the stuff on our window. This could be a GUI window, console window(, files, logs) or whatever. Python was programmed so the "print" expression calls to "sys.stdout.write" - This means we have to "take over" this function in order to print on the screen and write our own "plotter".

Follow the code, it's pretty self explanatory:

PyObject* log_CaptureStdout(PyObject* self, PyObject* pArgs)
 char* LogStr = NULL;
 if (!PyArg_ParseTuple(pArgs, "s", &LogStr)) return NULL;

 printf("%s", LogStr); 
 // Simply using printf to do the real work. 
 // You could also write it to a .log file or whatever...
 // MessageBox(NULL, LogStr...
 // WriteFile(hFile, LogStr...

 return Py_None;

// Notice we have STDERR too.
PyObject* log_CaptureStderr(PyObject* self, PyObject* pArgs)
 char* LogStr = NULL;
 if (!PyArg_ParseTuple(pArgs, "s", &LogStr)) return NULL;

 printf("%s", LogStr);

 return Py_None;

static PyMethodDef logMethods[] = {
 {"CaptureStdout", log_CaptureStdout, METH_VARARGS, "Logs stdout"},
 {"CaptureStderr", log_CaptureStderr, METH_VARARGS, "Logs stderr"},

 Py_InitModule("log", logMethods);
"import log\n"
"import sys\n"
"class StdoutCatcher:\n"
"\tdef write(self, str):\n"
"class StderrCatcher:\n"
"\tdef write(self, str):\n"
"sys.stdout = StdoutCatcher()\n"
"sys.stderr = StderrCatcher()\n"

Now everything that should be written to stdout will be written on the console(in our case) window. The Python code we actually running is simply to change the class of stdoud:

import log
import sys
class StdoutCatcher:
	def write(self, str):

class StderrCatcher:
	def write(self, str):

sys.stdout = StdoutCatcher() // Create a class object
sys.stderr = StderrCatcher() // Ditto

Hope you get the idea of what's going on.

Now you are pretty much on your on with Python. You can do whatever you are up to. Getting crazy with C functions and Python objects...


There is a small Python C API module regarding files. I want to show an example of how to use these functions. I was wondering how come that those API's asks for a FILE* where Python can be compiled with many different compilers (or supposes to). So what's wrong with it???

#include <Python.h>
#include <stdio.h>

void main()

 // Init logger...!

 // Open file for reading.
 PyObject* PyFileObject = PyFile_FromString("c:\\tempfile.txt", "r");
 if (PyFileObject == NULL) {
  // The file doesn't exist? An exception was thrown, 
  // we can ignore it and keep working, or we can return.
  // Let's ignore it for the sake of example, so the stderr won't "hear" of it.
  return FALSE;

 // Read the first line from the file.

 PyObject* PyLineObject = PyFile_GetLine(PyFileObject, 0);
 if (PyLineObject == NULL) {
  PyErr_Print(); // Print the exception if we couldn't read from the file.

 // Now print the string:
 printf("%s", PyString_AsString(PyLineObject));

 // Free the string:

 // Closing the file.

 retrun TRUE;

Now what about opening the file for execution? Like the Python's "execfile" ? Let's do it:

PyObject* ExecFile(PyObject* pSelf, PyObject* pArgs)
 char* FileName = NULL;
 if (!PyArg_ParseTuple(pArgs, "s", &FileName)) return NULL;

 PyObject* PyFileObject = PyFile_FromString(FileName, "r");
 if (PyFileObject == NULL) return NULL; // Let the user know the error.

 // Function Declration is: int PyRun_SimpleFile(FILE *fp, char *filename);
 // So where the hack should we get it a FILE* ? Therefore we have "PyFile_AsFile".
 PyRun_SimpleFile(PyFile_AsFile(PyFileObject), FileName);


 // Return TRUE.
 return Py_BuildValue("i", 1);

As I was searching for help, I didn't find anything on groups.google.com. And worse, many people were asking what to do in such a case... Hope I solved it for you!


Why would I want to use lists and Tuples? Leave me alone!
When you want to call a Python function from C(!) you'll have to give it a PyObject (speaking of "pArgs") variable which will point to a Tuple with all arguments required by the function.

And why a list?
Well let's say you want to get an endless number of arguments from the user, you can't use the "PyArg_ParseTuple" for that, what you have to do is to ask for an object which will be a list type object, and then you can read the parameters in it as you wish.

Let's begin with the Tuple object, we have to create a Tuple and set some of its arguments, and remember all in Python C API.

// We create a Tuple with two objects in it.
PyObject* MyTuple = PyTuple_New(2);
if (MyTuple == NULL) ; // Error

PyObject* MyInt = PyInt_FromLong(100);
if (MyInt == NULL) {
 // Don't forget to clean up
 // Error
PyTuple_SetItem(MyTuple, 0, MyInt);

PyObject* MyStr = PyString_FromString("Arkon");
if (MyStr == NULL) {
 // Don't forget to clean up
 // We don't need to free the int object, because the Tuple will take care of it.
 // Error
PyTuple_SetItem(MyTuple, 1, MyStr);

// Now we can call a function and give it the (int, string) parameters.
// Or do whatever we wish with it.

// This will also free the objects inside the Tuple itself, great isn't it?

The Python equivalent is:

MyTuple = (100, "Arkon")

Error handling sucks, I leave it for you, to handle it in an elegant way. Besides, I'm not sure you'll have to to check for every API calling, but if it's safer, why not? For the cost of a few additional lines. After all, it's your code. :)

Back to lists now, the code should be really similar:

PyObject* MyList = PyList_New(2);
if (MyList ... // Error
PyList_SetItem(MyList, 0, PyInt_FromLong(2003));
PyList_SetItem(MyList, 1, PyString_FromString("RageStorm"));

In Python:

MyList = [2003, "RageStorm"]

OK, so I know to create lists and Tuples, now what? Let's go futher, by now you should know the mechanism basics of the Python objects... And let's say we want to get a varying number of int object parameters. Just like "printf" can get using the Elipsis(... operator).

PyObject* Foo(PyObject* pSelf, PyObject* pArgs)
 PyObject* ArgsList = NULL;
 if (!PyArg_ParseTuple(pArgs, "O", &ArgsList)) return NULL; // We want to get one list object.

 // Making sure its type is a list.
 if (!PyList_Check(ArgsList)) {
  // Raise an exception in case it's not.
  PyErr_SetString(PyExc_StandardError, "The only parameter \"Foo\" gets, should be a list!");
  return NULL;

 // Scan all elements on that list, make sure they are int objects and print them:
 for (int i = 0; i < PyList_Size(ArgsList); i++) {
  // This is a BORROWED REFERENCE!!! Don't DECREF it later!
  PyObject* TmpInt = PyList_GetItem(ArgsList, i);
  if (!PyInt_Check(TmpInt)) {
   // Oh Uh, it's not an integer!
   char TmpStr[1024] = {0};
   sprintf(TmpStr, "Element #%d isn't an integer!", i);
   PyErr_SetString(PyExc_StandardError, TmpStr);
   return NULL;

  // Now that we know that we got an integer, let's print it.
  printf("%d ", PyInt_AsLong(TmpInt));

 // We are finished using the ArgsList, but we mustn't touch it, 
 // because we "told" PyArg_ParseTuple that we want to BORROW it!
 // So that's all folks. . .

 return Py_None;

Calling this function from Python could be as such:

// Valid:
Foo([1, 2, 3, 4])
Foo([1, 2])

// Invalid:
Foo(["1", 2, 3])
>>> StandardError: Element #0 isn't an integer!

Foo([1, 2], 3)
>>> TypeError: function takes exactly 1 argument (2 given)

>>> StandardError: The only parameter "Foo" gets, should be a list!


I didn't show you how to use Tuples for nothing, there is a reason, imagine we have an abstract Tank object in C++:

class Tank{
 Point pos;

 virtual void move()=0;
 virtual void draw()=0;

You get the idea... In this game we let our pre-defined script (the one which comes with the game) move the objects.

So it's up to the script to move the objects, but what we have to do everytime the move() C++ method is being called is calling the Python code of the Tank Move object.

If you have no idea what I'm talking about, let's make it simpler, you want to use hotkeys in your application, everytime a registered key combination is pressed you want to execute some Python code that the user supplied, which will do something in the application itself. Hope you get this one.

From both examples you have to call a Python function from C and use its result (if you want to), but how? For god sake! Now you say, "Ahh, that's why I need those Tuples", and, damn, you're right!

Let's do it:

// Say we have to supply two integers to the function:
PyObject* ArgsTuple = PyTuple_New(2);
PyTuple_SetItem(ArgsTuple, 0, PyInt_FromLong(1));
PyTuple_SetItem(ArgsTuple, 1, PyInt_FromLong(2));
PyObject* RetVal = PyEval_CallObject(CallbackFunction, ArgsTuple);

// You can do whatever you want with retval...Converting it to whatever C type variable.
// Don't forget to free it:

// And the tuple also:

Pretty easy, but what's about the "CallbackFunction" pointer, it's still missing...
Let's advance with "Foo", it will now ask for a callable Object, and save a reference to it in global
"CallbackFunction" pointer.

PyObject* CallbackFunction = NULL;
PyObject* Foo(PyObject* pSelf, PyObject* pArgs)
 if (!PyArg_ParseTuple(pArgs, "O", &CallbackFunction)) return NULL; // We want to get one callable object.

 if (!PyCallable_Check(CallbackFunction)) {
  // Error
  PyErr_SetString(PyExc_StandardError, "The object should be callable!");
  return NULL;

 // Finally, we are using the Py_INCREF. If we save a pointer to this function
 // and the user overrides it or deletes it, the pointer is lost (Pointing somewhere useless for us).
 // This is the reason why we increase the object's(function's) reference counter,
 // so it will be valid 'till the user tells us to unregister this function later.

 // Return TRUE
 return Py_BuildValue("i", 1);

Using our code in Python:

def x(Int1, Int2):
	print Int1, Int2

#Register the function

// Now call it in C with the above code snippet.

In this example we used a global PyObject pointer, but you should use it in an organized data structure
with the key combination to be pressed for example:

struct PyHotkey {
 char KeyCombination[32]; // Will hold something like "ctrl+alt+a".
 PyObject* Callback; // Will point to the function - when the key combination is pressed it will be called.

And finally, setting up your own scripting console. If you read through here, you should be mastering Python C API. So you don't really need me anymore... But if you want to see everything I just covered here (AND MORE!) in a decent application, check this out: Embedded Python!

After reading all this, you have to know that you have 3 new friends:

  1. This tutorial, nah, just kidding! :)
  2. groups.google.com - newsgroups
  3. And of course python.org

Oh part 2 is out, there you go: Embedded Python (Part 2)

User Contributed Comments(12)
 [1] arkon | 2005-11-16 12:59:33

About the exceptions, there are more types, of course, naming a few:
So you should use what fits you best.
 [2] arkon | 2005-11-17 17:11:10

Getting the line number of an exception could be done in Python like this:
import sys
print sys.exc_traceback.tb_lineno

The same code is appliable for Python's C API:
if (PyErr_Occurred()) {
PyObject* ptype;
PyObject* pvalue;
PyObject* ptraceback;
PyErr_Fetch(&ptype, &pvalue, &ptraceback);
printf("Error occurred on line: %d", ((PyTracebackObject*)ptraceback)->tb_lineno);
// Now you have two options, restoring the exception or disposing it.
PyErr_Restore(ptype, pvalue, ptraceback);


 [3] HazyNRG | 2006-12-10 05:18:33

In the first function of "Logging Stdout", you write:
 char* LogStr = NULL;
 if (!PyArg_ParseTuple(pArgs, "s", &LogStr)) return NULL;

 printf("%s", LogStr);

shouldn't it be:
 char* LogStr = NULL;
 if (!PyArg_ParseTuple(pArgs, "s", LogStr)) return NULL;

 printf("%s", *LogStr);
? because, you'd pass a pointer to a pointer to a char to PyArg_ParseTuple and a pointer to printf. correct me if i'm wrong, i'm new to C :P
 [4] HazyNRG | 2006-12-10 14:18:49

ok, i've just realized that i'm wrong. sorry
nice tutorial :)
 [5] Melih | 2007-11-05 10:36:58

Very helpful even more than python tutorial in reference count. I am glad to know that removing list would actually remove all the references inside.
 [6] codebreaker | 2008-01-14 00:33:43

HI,can i translate this into chinese and put it in my blog?
 [7] arkon | 2008-01-15 16:54:12

As long as you credit me and put a link to the original copy.
Good luck :)
 [8] codebreaker | 2008-01-25 02:43:38

In the documentation of Python C API you'll see that every function says if it returns a NEW REFERENCE or a BORROWED REFERENCE. You should follow it correctly or otherwise memory leaks will occur and other big no-no things.
sorry, i can't get it.
says ?no-no?others...
can you explain it for me?
 [9] arkon | 2008-01-25 11:01:35

a new reference means you have to free it on your own. thus an object was created and you're the owner, so you have to free it at the end. a borrowed reference means that you pass a pointer to the object you go, and you are the one who is still responsible for it.
 [10] BlackAnt | 2008-03-12 13:29:43

Came across your tutorial, its helped me to understand embedding and extending.
I've have one question:

  After compiling the your first tutorial via:-

  python setup.py build_ext -cmingw32

This compiles and produce the module as expected, but, when import the module
in python an error is generated saying:

  SystemError: dynamic module not initialized properly.

Have you ever come across this message and what could cause it?
 [11] Mike | 2008-04-15 07:01:17

Waaaw! This tutorial is so much better than the official documentation. I got stuff working in less than an hour. That is after messing around for two days with the official documentation. You might want to consider submitting this tutorial for integration into the official documentation.
 [12] jeff | 2009-05-29 16:16:36

Thanks a lot for this! I was in very much the same boat as Mike; without this rare tutorial I've got things off the ground finally!
Comments that will hurt anyone in any way will be deleted.
Don't ask for features, advertise or curse.
If you want to leave a message to the author use the contacts,
if you have any question in relation to your comments please use the forum.
Comments which violate any of these requests will be deleted without further
notice. Use the comment system decently.

Post your comment:
::Top 5 Tutorials::
Embedded Python[107335]
2D Rotated Rectangles Collision Detection[80431]
Keyboard Hook[69537]
HTTP Proxy[36588]

::Top 5 Samples::
2D ColDet Rotated Rectangles[10674]
PS2 Mouse Driver[6160]
Wave Format Player[5043]
Reading FAT12[4927]

All rights reserved to RageStorm © 2009