Xworld - Another World interpreter for Rockbox

Co-conspirators: Franklin Wei, Benjamin Brown

--------------------------------------------------------------------
This work is based on:
- Fabien Sanglard's "Fabother World" based on
- Piotr Padkowski's newRaw interpreter which was based on
- Gregory Montoir's reverse engineering of
- Eric Chahi's assembly code

--------------------------------------------------------------------
Progress:

* The plugin runs pretty nicely (with sound!) on most color targets
* Keymaps for color LCD targets are complete
* The manual entry is finished
* Grayscale/monochrome support is NOT PLANNED
  - the game looks horrible in grayscale! :p

--------------------------------------------------------------------
Notes:

* The original game strings were built-in to the executable, and
  were copyrighted and could not be used.
* This port ships with an alternate set of strings by default, but
  can load the "official" strings from a file at runtime.

--------------------------------------------------------------------
To be done (in descending order of importance):

* vertical stride compatibility                          <30% done>
* optimization                                           <10% done>

Change-Id: I3155b0d97c2ac470cb8a2040f40d4139ddcebfa5
Reviewed-on: http://gerrit.rockbox.org/1077
Reviewed-by: Michael Giacomelli <giac2000@hotmail.com>
diff --git a/apps/plugins/CATEGORIES b/apps/plugins/CATEGORIES
index 609f359..fd7a49a 100644
--- a/apps/plugins/CATEGORIES
+++ b/apps/plugins/CATEGORIES
@@ -134,4 +134,5 @@
 wavview,viewers
 wormlet,games
 xobox,games
+xworld,games
 zxbox,viewers
diff --git a/apps/plugins/SUBDIRS b/apps/plugins/SUBDIRS
index 8e65398..4b4015b 100644
--- a/apps/plugins/SUBDIRS
+++ b/apps/plugins/SUBDIRS
@@ -12,6 +12,12 @@
 /* For all targets with a bitmap display */
 #ifdef HAVE_LCD_BITMAP
 
+/* XWorld only supports color horizontal stride LCDs /for now/ ;) */
+#if (defined(HAVE_LCD_COLOR) && \
+    (!defined(LCD_STRIDEFORMAT) || (LCD_STRIDEFORMAT != VERTICAL_STRIDE))
+xworld
+#endif
+
 #if    (CONFIG_KEYPAD != ONDIO_PAD) /* not enough buttons */ \
     && (CONFIG_KEYPAD != SANSA_M200_PAD) /* not enough buttons */ \
     && (CONFIG_KEYPAD != HM60X_PAD) /* not enough buttons */ \
diff --git a/apps/plugins/xworld/README b/apps/plugins/xworld/README
new file mode 100644
index 0000000..3a07b1b
--- /dev/null
+++ b/apps/plugins/xworld/README
@@ -0,0 +1,86 @@
+This is the original readme from the "Fabother World" sources; the Rockbox port's
+readme is in README.rockbox.
+
+Franklin Wei
+
+=================================================================================
+
+This is "Fabother World": an Another World (Out Of This World in North America) interpreter codebase. This work is based on:
+
+- Piotr Padkowski's newRaw interpreter which was based on
+- Gregory Montoir's reverse engineering of
+- Eric Chahi's assembly code.
+
+I cleaned up a lot of the code, removing cryptic hexadecimal notation
+with meaningful macros name. I also cleanup a lot of the code so it has a 
+C/C++ philosophy instead of an assembly structure.
+
+I also created a Visual Studio 2010 project.
+
+TODO:
+
+Create a MacOS X project.
+Add a different rendering path OpenGL support.
+
+Fabien Sanglard
+
+
+raw README
+Release version: 0.1.1 (May 15 2004)
+-------------------------------------------------------------------------------
+
+About:
+------
+
+raw is a re-implementation of the engine used in the game Another World. This 
+game, released under the name Out Of This World in non-European countries, was 
+written by Eric Chahi at the beginning of the '90s. More information can be 
+found here : http://www.mobygames.com/game/sheet/p,2/gameId,564/.
+
+Please be aware that, currently, this implementation may contains bugs and 
+non-implemented features that make it impossible to finish the game.
+
+Supported Versions:
+-------------------
+
+Currently, only the english PC DOS version is supported ("Out of this World").
+
+Compiling:
+----------
+
+Tweak the Makefile if needed and type make (only gcc3 has been tested so far).
+The SDL and zlib libraries are required.
+
+Running:
+--------
+
+You will need the original files, here is the required list :
+    BANK*
+    MEMLIST.BIN
+	
+To start the game, you can either :
+- put the game's datafiles in the same directory as the executable
+- use the --datapath command line option to specify the datafiles directory
+
+Here are the various in game hotkeys :
+    Arrow Keys      allow you to move Lester
+    Enter/Space     allow you run/shoot with your gun
+    C               allow to enter a code to jump at a specific level
+    P               pause the game
+    Alt X           exit the game
+    Ctrl S          save game state
+    Ctrl L          load game state
+    Ctrl + and -    change game state slot
+    Ctrl F          toggle fast mode
+    Alt Enter       toggle windowed/fullscreen mode
+    Alt + and -     change scaler factor
+
+Credits:
+--------
+
+Eric Chahi, obviously, for making this great game.
+
+Contact:
+--------
+
+Gregory Montoir, cyx@users.sourceforge.net
diff --git a/apps/plugins/xworld/README.newraw b/apps/plugins/xworld/README.newraw
new file mode 100644
index 0000000..f5163c9
--- /dev/null
+++ b/apps/plugins/xworld/README.newraw
@@ -0,0 +1,4 @@
+
+Changes:
+  Added 2x and 3x high quality scalers (ripped from Reminescence)
+ 
diff --git a/apps/plugins/xworld/README.rockbox b/apps/plugins/xworld/README.rockbox
new file mode 100644
index 0000000..2ea63fa
--- /dev/null
+++ b/apps/plugins/xworld/README.rockbox
@@ -0,0 +1,51 @@
+This is the Rockbox port of Fabien Sanglard's "Fabother World", an Another World
+interpreter.
+
+Porting process:
+----------------
+
+The original code abstracted most of the platform-specific tasks, such as file I/O,
+sound, input, and video. However, the original code was in C++, so it was converted
+to C class-by-class. The conversion was attempted to be as conservative as possible,
+so little code was rewritten during the conversion process.
+
+Notes:
+------
+
+ - Optimization is badly needed.
+ - Vertical stride support is almost there.
+ - The game looks terrible in B+W/grayscale. This was the primary reason no attempt
+   was made to support these targets.
+ - The game does not run well on devices that have an LCD with a vertical stride.
+   - The M:Robe 500 is the only color device that meets this criterion, so it is
+     disabled by default.
+ - Sound doesn't sound 100% like the PC version. Perhaps the frequency reported to
+   the mixer is incorrect, or the buffer size is too big so that short sounds are
+   being missed.
+
+To do (in no particular order):
+-------------------------------
+
+ - Support vertical stride LCD's
+ - Support grayscale/monochrome LCD's
+ - Optimize
+
+Credits:
+--------
+
+**************************************
+**************************************
+********** !!!ERIC CHAHI!!! **********
+**************************************
+**************************************
+<the original author of Another World>
+
+Gregory Montoir
+Piotr Padkowski
+Fabien Sanglard
+
+Rockbox porters:
+----------------
+
+Franklin Wei
+Benjamin Brown
diff --git a/apps/plugins/xworld/SOURCES b/apps/plugins/xworld/SOURCES
new file mode 100644
index 0000000..98b5f14
--- /dev/null
+++ b/apps/plugins/xworld/SOURCES
@@ -0,0 +1,15 @@
+bank.c
+engine.c
+file.c
+intern.c
+mixer.c
+parts.c
+resource.c
+serializer.c
+sfxplayer.c
+sys.c
+util.c
+video.c
+video_data.c
+vm.c
+xworld.c
diff --git a/apps/plugins/xworld/awendian.h b/apps/plugins/xworld/awendian.h
new file mode 100644
index 0000000..863b19b
--- /dev/null
+++ b/apps/plugins/xworld/awendian.h
@@ -0,0 +1,51 @@
+/***************************************************************************
+ *             __________               __   ___.
+ *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
+ *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
+ *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
+ *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
+ *                     \/            \/     \/    \/            \/
+ * $Id$
+ *
+ * Copyright (C) 2014 Franklin Wei, Benjamin Brown
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ***************************************************************************/
+
+#ifndef __SYS_H__
+#define __SYS_H__
+
+#include "rbendian.h"
+#include "stdint.h"
+
+
+#ifdef ROCKBOX_LITTLE_ENDIAN
+#define SYS_LITTLE_ENDIAN
+#else
+#define SYS_BIG_ENDIAN
+#endif
+
+
+#if defined SYS_LITTLE_ENDIAN
+#define READ_BE_UINT16(p) ((((const uint8_t*)p)[0] << 8) | ((const uint8_t*)p)[1])
+#define READ_BE_UINT32(p) ((((const uint8_t*)p)[0] << 24) | (((const uint8_t*)p)[1] << 16) | (((const uint8_t*)p)[2] << 8) | ((const uint8_t*)p)[3])
+
+#elif defined SYS_BIG_ENDIAN
+
+#define READ_BE_UINT16(p) (*(const uint16_t*)p)
+#define READ_BE_UINT32(p) (*(const uint32_t*)p)
+
+#else
+
+#error No endianness defined
+
+#endif
+
+#endif
diff --git a/apps/plugins/xworld/bank.c b/apps/plugins/xworld/bank.c
new file mode 100644
index 0000000..e2cfbd4
--- /dev/null
+++ b/apps/plugins/xworld/bank.c
@@ -0,0 +1,153 @@
+/***************************************************************************
+ *             __________               __   ___.
+ *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
+ *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
+ *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
+ *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
+ *                     \/            \/     \/    \/            \/
+ * $Id$
+ *
+ * Copyright (C) 2014 Franklin Wei, Benjamin Brown
+ * Copyright (C) 2004 Gregory Montoir
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ***************************************************************************/
+
+#include "plugin.h"
+#include "bank.h"
+#include "file.h"
+#include "resource.h"
+
+void bank_create(struct Bank* b, const char *dataDir)
+{
+    b->_dataDir = dataDir;
+}
+
+bool bank_read(struct Bank* b, const struct MemEntry *me, uint8_t *buf) {
+
+    bool ret = false;
+    char bankName[10];
+    rb->snprintf(bankName, 10, "bank%02x", me->bankId);
+    File f;
+    file_create(&f, false);
+    if (!file_open(&f, bankName, b->_dataDir, "rb"))
+        error("bank_read() unable to open '%s' in dir '%s'", bankName, b->_dataDir);
+
+    file_seek(&f, me->bankOffset);
+
+    /* Depending if the resource is packed or not we */
+    /* can read directly or unpack it. */
+    if (me->packedSize == me->size) {
+        file_read(&f, buf, me->packedSize);
+        ret = true;
+    } else {
+        file_read(&f, buf, me->packedSize);
+        b->_startBuf = buf;
+        b->_iBuf = buf + me->packedSize - 4;
+        ret = bank_unpack(b);
+    }
+    file_close(&f);
+    return ret;
+}
+
+void bank_decUnk1(struct Bank* b, uint8_t numChunks, uint8_t addCount) {
+    uint16_t count = bank_getCode(b, numChunks) + addCount + 1;
+    debug(DBG_BANK, "bank_decUnk1(%d, %d) count=%d", numChunks, addCount, count);
+    b->_unpCtx.datasize -= count;
+    while (count--) {
+        assert(b->_oBuf >= b->_iBuf && b->_oBuf >= b->_startBuf);
+        *b->_oBuf = (uint8_t)bank_getCode(b, 8);
+        --b->_oBuf;
+    }
+}
+
+/*
+  Note from fab: This look like run-length encoding.
+*/
+void bank_decUnk2(struct Bank* b, uint8_t numChunks) {
+    uint16_t i = bank_getCode(b, numChunks);
+    uint16_t count = b->_unpCtx.size + 1;
+    debug(DBG_BANK, "bank_decUnk2(%d) i=%d count=%d", numChunks, i, count);
+    b->_unpCtx.datasize -= count;
+    while (count--) {
+        assert(b->_oBuf >= b->_iBuf && b->_oBuf >= b->_startBuf);
+        *b->_oBuf = *(b->_oBuf + i);
+        --b->_oBuf;
+    }
+}
+
+/*
+  Most resource in the banks are compacted.
+*/
+bool bank_unpack(struct Bank* b) {
+    b->_unpCtx.size = 0;
+    b->_unpCtx.datasize = READ_BE_UINT32(b->_iBuf);
+    b->_iBuf -= 4;
+    b->_oBuf = b->_startBuf + b->_unpCtx.datasize - 1;
+    b->_unpCtx.crc = READ_BE_UINT32(b->_iBuf);
+    b->_iBuf -= 4;
+    b->_unpCtx.chk = READ_BE_UINT32(b->_iBuf);
+    b->_iBuf -= 4;
+    b->_unpCtx.crc ^= b->_unpCtx.chk;
+    do {
+        if (!bank_nextChunk(b)) {
+            b->_unpCtx.size = 1;
+            if (!bank_nextChunk(b)) {
+                bank_decUnk1(b, 3, 0);
+            } else {
+                bank_decUnk2(b, 8);
+            }
+        } else {
+            uint16_t c = bank_getCode(b, 2);
+            if (c == 3) {
+                bank_decUnk1(b, 8, 8);
+            } else {
+                if (c < 2) {
+                    b->_unpCtx.size = c + 2;
+                    bank_decUnk2(b, c + 9);
+                } else {
+                    b->_unpCtx.size = bank_getCode(b, 8);
+                    bank_decUnk2(b, 12);
+                }
+            }
+        }
+    } while (b->_unpCtx.datasize > 0);
+    return (b->_unpCtx.crc == 0);
+}
+
+uint16_t bank_getCode(struct Bank* b, uint8_t numChunks) {
+    uint16_t c = 0;
+    while (numChunks--) {
+        c <<= 1;
+        if (bank_nextChunk(b)) {
+            c |= 1;
+        }
+    }
+    return c;
+}
+
+bool bank_nextChunk(struct Bank* b) {
+    bool CF = bank_rcr(b, false);
+    if (b->_unpCtx.chk == 0) {
+        assert(b->_iBuf >= b->_startBuf);
+        b->_unpCtx.chk = READ_BE_UINT32(b->_iBuf);
+        b->_iBuf -= 4;
+        b->_unpCtx.crc ^= b->_unpCtx.chk;
+        CF = bank_rcr(b, true);
+    }
+    return CF;
+}
+
+bool bank_rcr(struct Bank* b, bool CF) {
+    bool rCF = (b->_unpCtx.chk & 1);
+    b->_unpCtx.chk >>= 1;
+    if (CF) b->_unpCtx.chk |= 0x80000000;
+    return rCF;
+}
diff --git a/apps/plugins/xworld/bank.h b/apps/plugins/xworld/bank.h
new file mode 100644
index 0000000..6f280c5
--- /dev/null
+++ b/apps/plugins/xworld/bank.h
@@ -0,0 +1,55 @@
+/***************************************************************************
+ *             __________               __   ___.
+ *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
+ *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
+ *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
+ *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
+ *                     \/            \/     \/    \/            \/
+ * $Id$
+ *
+ * Copyright (C) 2014 Franklin Wei, Benjamin Brown
+ * Copyright (C) 2004 Gregory Montoir
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ***************************************************************************/
+
+#ifndef __BANK_H__
+#define __BANK_H__
+
+#include "intern.h"
+
+struct MemEntry;
+
+struct UnpackContext {
+    uint16_t size;
+    uint32_t crc;
+    uint32_t chk;
+    int32_t datasize;
+};
+
+struct Bank
+{
+    struct UnpackContext _unpCtx;
+    const char *_dataDir;
+    uint8_t *_iBuf, *_oBuf, *_startBuf;
+};
+
+/* needs allocated memory */
+void bank_create(struct Bank*, const char *dataDir);
+
+bool bank_read(struct Bank*, const struct MemEntry *me, uint8_t *buf);
+void bank_decUnk1(struct Bank*, uint8_t numChunks, uint8_t addCount);
+void bank_decUnk2(struct Bank*, uint8_t numChunks);
+bool bank_unpack(struct Bank*);
+uint16_t bank_getCode(struct Bank*, uint8_t numChunks);
+bool bank_nextChunk(struct Bank*);
+bool bank_rcr(struct Bank*, bool CF);
+
+#endif
diff --git a/apps/plugins/xworld/engine.c b/apps/plugins/xworld/engine.c
new file mode 100644
index 0000000..0d1c1bf
--- /dev/null
+++ b/apps/plugins/xworld/engine.c
@@ -0,0 +1,397 @@
+/***************************************************************************
+ *             __________               __   ___.
+ *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
+ *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
+ *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
+ *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
+ *                     \/            \/     \/    \/            \/
+ * $Id$
+ *
+ * Copyright (C) 2014 Franklin Wei, Benjamin Brown
+ * Copyright (C) 2004 Gregory Montoir
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ***************************************************************************/
+
+#include "plugin.h"
+#include "engine.h"
+#include "file.h"
+#include "serializer.h"
+#include "sys.h"
+#include "parts.h"
+#include "video_data.h"
+#include "video.h"
+
+void engine_create(struct Engine* e, struct System* stub, const char* dataDir, const char* saveDir)
+{
+    e->sys = stub;
+    e->sys->e = e;
+    e->_dataDir = dataDir;
+    e->_saveDir = saveDir;
+
+    mixer_create(&e->mixer, e->sys);
+
+    /* this needs to be here and not engine_init() to ensure that it is not called on a reset */
+    res_create(&e->res, &e->video, e->sys, dataDir);
+
+    res_allocMemBlock(&e->res);
+
+    video_create(&e->video, &e->res, e->sys);
+
+    player_create(&e->player, &e->mixer, &e->res, e->sys);
+
+    vm_create(&e->vm, &e->mixer, &e->res, &e->player, &e->video, e->sys);
+}
+
+void engine_run(struct Engine* e) {
+
+    while (!e->sys->input.quit) {
+
+        vm_checkThreadRequests(&e->vm);
+
+        vm_inp_updatePlayer(&e->vm);
+
+        engine_processInput(e);
+
+        vm_hostFrame(&e->vm);
+    }
+
+}
+
+/*
+ * this function loads the font in XWORLD_FONT_FILE into video_font
+ */
+
+/*
+ * the file format for the font file is like this:
+ * "XFNT" magic
+ * 8-bit version number
+ * <768 bytes data>
+ * sum of data, XOR'ed by version number repeated 4 times (32-bit)
+ */
+bool engine_loadFontFile(struct Engine* e)
+{
+    uint8_t *old_font = sys_get_buffer(e->sys, sizeof(video_font));
+    rb->memcpy(old_font, video_font, sizeof(video_font));
+
+    File f;
+    file_create(&f, false);
+    if(!file_open(&f, XWORLD_FONT_FILE, e->_dataDir, "rb"))
+    {
+        goto fail;
+    }
+
+    /* read header */
+    char header[5];
+    int ret = file_read(&f, header, sizeof(header));
+    if(ret != sizeof(header) ||
+            header[0] != 'X' ||
+            header[1] != 'F' ||
+            header[2] != 'N' ||
+            header[3] != 'T')
+    {
+        warning("Invalid font file signature, falling back to alternate font");
+        goto fail;
+    }
+
+    if(header[4] != XWORLD_FONT_VERSION)
+    {
+        warning("Font file version mismatch (have=%d, need=%d), falling back to alternate font", header[4], XWORLD_FONT_VERSION);
+        goto fail;
+    }
+
+    uint32_t sum = 0;
+    for(unsigned int i = 0;i<sizeof(video_font);++i)
+    {
+        sum += video_font[i] = file_readByte(&f);
+    }
+
+    uint32_t mask = (header[4] << 24) |
+                    (header[4] << 16) |
+                    (header[4] << 8 ) |
+                    (header[4] << 0 );
+    sum ^= mask;
+    uint32_t check = file_readUint32BE(&f);
+
+    if(check != sum)
+    {
+        warning("Bad font checksum, falling back to alternate font");
+        goto fail;
+    }
+
+    file_close(&f);
+    return true;
+
+fail:
+    file_close(&f);
+
+    memcpy(video_font, old_font, sizeof(video_font));
+    return false;
+}
+
+/*
+ * this function loads the string table in STRING_TABLE_FILE into
+ * video_stringsTableEng
+ */
+
+/*
+ * the file format for the string table is like this:
+ * "XWST" magic
+ * 8-bit version number
+ * 8-bit title length
+ * <title data (0-255 bytes, _NO NULL_)
+ * 16-bit number of string entries (currently limited to 255)
+ * entry format:
+   struct file_entry_t
+   {
+     uint16_t id;
+     uint16_t len; - length of str
+     char* str;    - NO NULL
+   }
+*/
+bool engine_loadStringTable(struct Engine* e)
+{
+    File f;
+    file_create(&f, false);
+    if(!file_open(&f, STRING_TABLE_FILE, e->_dataDir, "rb"))
+    {
+        /*
+         * this gives verbose warnings while loadFontFile doesn't because the font looks similar
+         * enough to pass for the "original", but the strings don't
+         */
+        warning("Unable to find string table, falling back to alternate strings");
+        goto fail;
+    }
+
+    /* read header */
+
+    char header[5];
+    int ret = file_read(&f, header, sizeof(header));
+    if(ret != sizeof(header) ||
+            header[0] != 'X' ||
+            header[1] != 'W' ||
+            header[2] != 'S' ||
+            header[3] != 'T')
+    {
+        warning("Invalid string table signature, falling back to alternate strings");
+        goto fail;
+    }
+
+    if(header[4] != STRING_TABLE_VERSION)
+    {
+        warning("String table version mismatch (have=%d, need=%d), falling back to alternate strings", header[4], STRING_TABLE_VERSION);
+        goto fail;
+    }
+
+    /* read title */
+
+    uint8_t title_length = file_readByte(&f);
+    char *title_buf;
+    if(title_length)
+    {
+        title_buf = sys_get_buffer(e->sys, (int32_t)title_length + 1); /* make room for the NULL */
+        ret = file_read(&f, title_buf, title_length);
+        if(ret != title_length)
+        {
+            warning("Title shorter than expected, falling back to alternate strings");
+            goto fail;
+        }
+    }
+    else
+    {
+        title_buf = "UNKNOWN";
+    }
+
+    /* read entries */
+
+    uint16_t num_entries = file_readUint16BE(&f);
+    for(unsigned int i = 0; i < num_entries && i < ARRAYLEN(video_stringsTableEng); ++i)
+    {
+        video_stringsTableEng[i].id = file_readUint16BE(&f);
+        uint16_t len                = file_readUint16BE(&f);
+
+        if(file_ioErr(&f))
+        {
+            warning("Unexpected EOF in while parsing entry %d, falling back to alternate strings", i);
+            goto fail;
+        }
+
+        video_stringsTableEng[i].str = sys_get_buffer(e->sys, (int32_t)len + 1);
+
+        ret = file_read(&f, video_stringsTableEng[i].str, len);
+        if(ret != len)
+        {
+            warning("Entry %d too short, falling back to alternate strings", i);
+            goto fail;
+        }
+    }
+
+    file_close(&f);
+    rb->splashf(HZ, "String table '%s' loaded", title_buf);
+    return true;
+fail:
+    file_close(&f);
+    return false;
+}
+
+void engine_init(struct Engine* e) {
+    sys_init(e->sys, "Out Of This World");
+
+    res_readEntries(&e->res);
+
+    engine_loadStringTable(e);
+
+    engine_loadFontFile(e);
+
+    video_init(&e->video);
+
+    vm_init(&e->vm);
+
+    mixer_init(&e->mixer);
+
+    player_init(&e->player);
+
+    /* Init virtual machine, legacy way */
+    /* vm_initForPart(&e->vm, GAME_PART_FIRST); // This game part is the protection screen */
+
+    /*  Try to cheat here. You can jump anywhere but the VM crashes afterward. */
+    /*  Starting somewhere is probably not enough, the variables and calls return are probably missing. */
+    /* vm_initForPart(&e->vm, GAME_PART2);  Skip protection screen and go directly to intro */
+    /* vm_initForPart(&e->vm, GAME_PART3);  CRASH */
+    /* vm_initForPart(&e->vm, GAME_PART4);  Start directly in jail but then crash */
+    /* vm->initForPart(&e->vm, GAME_PART5);   CRASH */
+    /* vm->initForPart(GAME_PART6);    Start in the battlechar but CRASH afteward */
+    /* vm->initForPart(GAME_PART7); CRASH */
+    /* vm->initForPart(GAME_PART8); CRASH */
+    /* vm->initForPart(GAME_PART9);  Green screen not doing anything */
+}
+
+void engine_finish(struct Engine* e) {
+    player_free(&e->player);
+    mixer_free(&e->mixer);
+    res_freeMemBlock(&e->res);
+}
+
+void engine_processInput(struct Engine* e) {
+    if (e->sys->input.load) {
+        engine_loadGameState(e, e->_stateSlot);
+        e->sys->input.load = false;
+    }
+    if (e->sys->input.save) {
+        engine_saveGameState(e, e->_stateSlot, "quicksave");
+        e->sys->input.save = false;
+    }
+    if (e->sys->input.fastMode) {
+        e->vm._fastMode = !&e->vm._fastMode;
+        e->sys->input.fastMode = false;
+    }
+    if (e->sys->input.stateSlot != 0) {
+        int8_t slot = e->_stateSlot + e->sys->input.stateSlot;
+        if (slot >= 0 && slot < MAX_SAVE_SLOTS) {
+            e->_stateSlot = slot;
+            debug(DBG_INFO, "Current game state slot is %d", e->_stateSlot);
+        }
+        e->sys->input.stateSlot = 0;
+    }
+}
+
+void engine_makeGameStateName(struct Engine* e, uint8_t slot, char *buf, int sz) {
+    (void) e;
+    rb->snprintf(buf, sz, "xworld_save.s%02d", slot);
+}
+
+void engine_saveGameState(struct Engine* e, uint8_t slot, const char *desc) {
+    char stateFile[20];
+    /* sizeof(char) is guaranteed to be 1 */
+    engine_makeGameStateName(e, slot, stateFile, sizeof(stateFile));
+    File f;
+    file_create(&f, false);
+    if (!file_open(&f, stateFile, e->_saveDir, "wb")) {
+        warning("Unable to save state file '%s'", stateFile);
+    } else {
+        /* header */
+        file_writeUint32BE(&f, SAVE_MAGIC);
+        file_writeUint16BE(&f, CUR_VER);
+        file_writeUint16BE(&f, 0);
+        char hdrdesc[32];
+        strncpy(hdrdesc, desc, sizeof(hdrdesc) - 1);
+        file_write(&f, hdrdesc, sizeof(hdrdesc));
+        /* contents */
+        struct Serializer s;
+        ser_create(&s, &f, SM_SAVE, e->res._memPtrStart, CUR_VER);
+        vm_saveOrLoad(&e->vm, &s);
+        res_saveOrLoad(&e->res, &s);
+        video_saveOrLoad(&e->video, &s);
+        player_saveOrLoad(&e->player, &s);
+        mixer_saveOrLoad(&e->mixer, &s);
+        if (file_ioErr(&f)) {
+            warning("I/O error when saving game state");
+        } else {
+            debug(DBG_INFO, "Saved state to slot %d", e->_stateSlot);
+        }
+    }
+    file_close(&f);
+}
+
+bool engine_loadGameState(struct Engine* e, uint8_t slot) {
+    char stateFile[20];
+    engine_makeGameStateName(e, slot, stateFile, 20);
+    File f;
+    file_create(&f, false);
+    if (!file_open(&f, stateFile, e->_saveDir, "rb")) {
+        debug(DBG_ENG, "Unable to open state file '%s'", stateFile);
+        goto fail;
+    } else {
+        uint32_t id = file_readUint32BE(&f);
+        if (id != SAVE_MAGIC) {
+            debug(DBG_ENG, "Bad savegame format");
+            goto fail;
+        } else {
+            /* mute */
+            player_stop(&e->player);
+            mixer_stopAll(&e->mixer);
+            /* header */
+            uint16_t ver = file_readUint16BE(&f);
+            file_readUint16BE(&f);
+            char hdrdesc[32];
+            file_read(&f, hdrdesc, sizeof(hdrdesc));
+            /* contents */
+            struct Serializer s;
+            ser_create(&s, &f, SM_LOAD, e->res._memPtrStart, ver);
+            vm_saveOrLoad(&e->vm, &s);
+            res_saveOrLoad(&e->res, &s);
+            video_saveOrLoad(&e->video, &s);
+            player_saveOrLoad(&e->player, &s);
+            mixer_saveOrLoad(&e->mixer, &s);
+        }
+        if (file_ioErr(&f)) {
+            debug(DBG_ENG, "I/O error when loading game state");
+            goto fail;
+        } else {
+            debug(DBG_INFO, "Loaded state from slot %d", e->_stateSlot);
+        }
+    }
+    file_close(&f);
+    return true;
+fail:
+    file_close(&f);
+    return false;
+}
+
+void engine_deleteGameState(struct Engine* e, uint8_t slot) {
+    char stateFile[20];
+    engine_makeGameStateName(e, slot, stateFile, 20);
+    file_remove(stateFile, e->_saveDir);
+}
+
+const char* engine_getDataDir(struct Engine* e)
+{
+    return e->_dataDir;
+}
diff --git a/apps/plugins/xworld/engine.h b/apps/plugins/xworld/engine.h
new file mode 100644
index 0000000..a0ad2b4
--- /dev/null
+++ b/apps/plugins/xworld/engine.h
@@ -0,0 +1,70 @@
+/***************************************************************************
+ *             __________               __   ___.
+ *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
+ *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
+ *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
+ *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
+ *                     \/            \/     \/    \/            \/
+ * $Id$
+ *
+ * Copyright (C) 2014 Franklin Wei, Benjamin Brown
+ * Copyright (C) 2004 Gregory Montoir
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ***************************************************************************/
+
+#ifndef __ENGINE_H__
+#define __ENGINE_H__
+
+#include "intern.h"
+#include "vm.h"
+#include "mixer.h"
+#include "sfxplayer.h"
+#include "resource.h"
+#include "video.h"
+#include "sys.h"
+
+#define STRING_TABLE_FILE "xworld.strings" /* this is relative to dataDir */
+#define STRING_TABLE_VERSION 0x03
+
+#define XWORLD_FONT_FILE "xworld.font" /* relative to dataDir */
+#define XWORLD_FONT_VERSION 0x01
+
+struct System;
+
+#define MAX_SAVE_SLOTS 1
+#define SAVE_MAGIC 0x42424657
+struct Engine {
+    struct System *sys;
+    struct VirtualMachine vm;
+    struct Mixer mixer;
+    struct Resource res;
+    struct SfxPlayer player;
+    struct Video video;
+    const char *_dataDir, *_saveDir;
+    uint8_t _stateSlot;
+};
+
+void engine_create(struct Engine* e, struct System* stub, const char* dataDir, const char* saveDir);
+
+void engine_run(struct Engine*);
+void engine_init(struct Engine*);
+void engine_finish(struct Engine*);
+void engine_processInput(struct Engine*);
+
+bool engine_loadFontFile(struct Engine*);
+bool engine_loadStringTable(struct Engine*);
+
+void engine_makeGameStateName(struct Engine*, uint8_t slot, char *buf, int sz);
+void engine_saveGameState(struct Engine*, uint8_t slot, const char *desc);
+bool engine_loadGameState(struct Engine*, uint8_t slot);
+void engine_deleteGameState(struct Engine*, uint8_t slot);
+const char* engine_getDataDir(struct Engine*);
+#endif
diff --git a/apps/plugins/xworld/file.c b/apps/plugins/xworld/file.c
new file mode 100644
index 0000000..8353544
--- /dev/null
+++ b/apps/plugins/xworld/file.c
@@ -0,0 +1,172 @@
+/***************************************************************************
+ *             __________               __   ___.
+ *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
+ *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
+ *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
+ *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
+ *                     \/            \/     \/    \/            \/
+ * $Id$
+ *
+ * Copyright (C) 2014 Franklin Wei, Benjamin Brown
+ * Copyright (C) 2004 Gregory Montoir
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ***************************************************************************/
+
+#include "plugin.h"
+#include "file.h"
+
+void file_create(struct File* f, bool gzipped) {
+    f->gzipped = gzipped;
+    f->fd = -1;
+    f->ioErr = false;
+}
+
+bool file_open(struct File* f, const char *filename, const char *directory, const char *mode) {
+    char buf[512];
+    rb->snprintf(buf, 512, "%s/%s", directory, filename);
+    char *p = buf + rb->strlen(directory) + 1;
+    string_lower(p);
+
+    int flags = 0;
+    for(int i = 0; mode[i]; ++i)
+    {
+        switch(mode[i])
+        {
+        case 'w':
+            flags |= O_WRONLY | O_CREAT | O_TRUNC;
+            break;
+        case 'r':
+            flags |= O_RDONLY;
+            break;
+        default:
+            break;
+        }
+    }
+    f->fd = -1;
+    debug(DBG_FILE, "trying %s first", buf);
+    f->fd = rb->open(buf, flags, 0666);
+    if (f->fd < 0) { // let's try uppercase
+        string_upper(p);
+        debug(DBG_FILE, "now trying %s uppercase", buf);
+        f->fd = rb->open(buf, flags, 0666);
+    }
+    if(f->fd > 0)
+        return true;
+    else
+        return false;
+}
+
+void file_close(struct File* f) {
+    if(f->gzipped)
+    {
+    }
+    else
+    {
+        rb->close(f->fd);
+    }
+}
+
+bool file_ioErr(struct File* f) {
+    return f->ioErr;
+}
+
+void file_seek(struct File* f, int32_t off) {
+    if(f->gzipped)
+    {
+    }
+    else
+    {
+        rb->lseek(f->fd, off, SEEK_SET);
+    }
+}
+int file_read(struct File* f, void *ptr, uint32_t size) {
+    if(f->gzipped)
+    {
+        return -1;
+    }
+    else
+    {
+        unsigned int rc = rb->read(f->fd, ptr, size);
+        if(rc != size)
+            f->ioErr = true;
+        return rc;
+    }
+}
+uint8_t file_readByte(struct File* f) {
+    uint8_t b;
+    if(f->gzipped)
+    {
+        b = 0xff;
+    }
+    else
+    {
+        if(rb->read(f->fd, &b, 1) != 1)
+        {
+            f->ioErr = true;
+            debug(DBG_FILE, "file read failed");
+        }
+    }
+    return b;
+}
+
+uint16_t file_readUint16BE(struct File* f) {
+    uint8_t hi = file_readByte(f);
+    uint8_t lo = file_readByte(f);
+    return (hi << 8) | lo;
+}
+
+uint32_t file_readUint32BE(struct File* f) {
+    uint16_t hi = file_readUint16BE(f);
+    uint16_t lo = file_readUint16BE(f);
+    return (hi << 16) | lo;
+}
+
+int file_write(struct File* f, void *ptr, uint32_t size) {
+    if(f->gzipped)
+    {
+        return 0;
+    }
+    else
+    {
+        return rb->write(f->fd, ptr, size);
+    }
+}
+
+void file_writeByte(struct File* f, uint8_t b) {
+    file_write(f, &b, 1);
+}
+
+void file_writeUint16BE(struct File* f, uint16_t n) {
+    file_writeByte(f, n >> 8);
+    file_writeByte(f, n & 0xFF);
+}
+
+void file_writeUint32BE(struct File* f, uint32_t n) {
+    file_writeUint16BE(f, n >> 16);
+    file_writeUint16BE(f, n & 0xFFFF);
+}
+
+void file_remove(const char* filename, const char* directory)
+{
+    char buf[512];
+    rb->snprintf(buf, 512, "%s/%s", directory, filename);
+    char *p = buf + rb->strlen(directory) + 1;
+    string_lower(p);
+    if(rb->file_exists(buf))
+    {
+        rb->remove(buf);
+    }
+    else
+    {
+        string_upper(p);
+        rb->remove(buf);
+    }
+}
diff --git a/apps/plugins/xworld/file.h b/apps/plugins/xworld/file.h
new file mode 100644
index 0000000..b24cdaf
--- /dev/null
+++ b/apps/plugins/xworld/file.h
@@ -0,0 +1,51 @@
+/***************************************************************************
+ *             __________               __   ___.
+ *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
+ *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
+ *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
+ *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
+ *                     \/            \/     \/    \/            \/
+ * $Id$
+ *
+ * Copyright (C) 2014 Franklin Wei, Benjamin Brown
+ * Copyright (C) 2004 Gregory Montoir
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ***************************************************************************/
+
+#ifndef __FILE_H__
+#define __FILE_H__
+
+#include "intern.h"
+
+typedef struct File {
+    int fd;
+    bool gzipped;
+    bool ioErr;
+} File;
+
+void file_create(struct File*, bool gzipped);
+
+bool file_open(struct File*, const char *filename, const char *directory, const char *mode);
+void file_close(struct File*);
+bool file_ioErr(struct File*);
+void file_seek(struct File*, int32_t off);
+int file_read(struct File*, void *ptr, uint32_t size);
+uint8_t file_readByte(struct File*);
+uint16_t file_readUint16BE(struct File*);
+uint32_t file_readUint32BE(struct File*);
+int file_write(struct File*, void *ptr, uint32_t size);
+void file_writeByte(struct File*, uint8_t b);
+void file_writeUint16BE(struct File*, uint16_t n);
+void file_writeUint32BE(struct File*, uint32_t n);
+
+void file_remove(const char* filename, const char* directory);
+
+#endif
diff --git a/apps/plugins/xworld/intern.c b/apps/plugins/xworld/intern.c
new file mode 100644
index 0000000..0d58100
--- /dev/null
+++ b/apps/plugins/xworld/intern.c
@@ -0,0 +1,34 @@
+/***************************************************************************
+ *             __________               __   ___.
+ *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
+ *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
+ *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
+ *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
+ *                     \/            \/     \/    \/            \/
+ * $Id$
+ *
+ * Copyright (C) 2014 Franklin Wei, Benjamin Brown
+ * Copyright (C) 2004 Gregory Montoir
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ***************************************************************************/
+
+#include "intern.h"
+#include "awendian.h"
+
+uint8_t ICODE_ATTR scriptPtr_fetchByte(struct Ptr* p) {
+    return *p->pc++;
+}
+
+uint16_t ICODE_ATTR scriptPtr_fetchWord(struct Ptr* p) {
+    uint16_t i = READ_BE_UINT16(p->pc);
+    p->pc += 2;
+    return i;
+}
diff --git a/apps/plugins/xworld/intern.h b/apps/plugins/xworld/intern.h
new file mode 100644
index 0000000..3355f28
--- /dev/null
+++ b/apps/plugins/xworld/intern.h
@@ -0,0 +1,44 @@
+/***************************************************************************
+ *             __________               __   ___.
+ *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
+ *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
+ *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
+ *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
+ *                     \/            \/     \/    \/            \/
+ * $Id$
+ *
+ * Copyright (C) 2014 Franklin Wei, Benjamin Brown
+ * Copyright (C) 2004 Gregory Montoir
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ***************************************************************************/
+
+#ifndef __INTERN_H__
+#define __INTERN_H__
+
+#include "plugin.h"
+#include "string.h"
+#include "awendian.h"
+#include "util.h"
+
+#define assert(c) (c?(void)0:error("Assertion failed line %d, file %s", __LINE__, __FILE__))
+
+struct Ptr {
+    uint8_t* pc;
+};
+
+uint8_t scriptPtr_fetchByte(struct Ptr* p) ICODE_ATTR;
+uint16_t scriptPtr_fetchWord(struct Ptr* p) ICODE_ATTR;
+
+struct Point {
+    int16_t x, y;
+};
+
+#endif
diff --git a/apps/plugins/xworld/keymaps.h b/apps/plugins/xworld/keymaps.h
new file mode 100644
index 0000000..edba05b
--- /dev/null
+++ b/apps/plugins/xworld/keymaps.h
@@ -0,0 +1,183 @@
+/***************************************************************************
+ *             __________               __   ___.
+ *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
+ *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
+ *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
+ *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
+ *                     \/            \/     \/    \/            \/
+ * $Id$
+ *
+ * Copyright (C) 2014 Franklin Wei, Benjamin Brown
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ***************************************************************************/
+
+#ifndef _XWORLD_KEYMAPS_H
+#define _XWORLD_KEYMAPS_H
+#endif
+
+#if (CONFIG_KEYPAD == PHILIPS_HDD1630_PAD)  || \
+    (CONFIG_KEYPAD == PHILIPS_HDD6330_PAD)  || \
+    (CONFIG_KEYPAD == PHILIPS_SA9200_PAD)   || \
+    (CONFIG_KEYPAD == CREATIVE_ZENXFI2_PAD) || \
+    (CONFIG_KEYPAD == CREATIVE_ZENXFI3_PAD) || \
+    (CONFIG_KEYPAD == SANSA_CONNECT_PAD)    || \
+    (CONFIG_KEYPAD == SANSA_C200_PAD)       || \
+    (CONFIG_KEYPAD == SANSA_CLIP_PAD)       || \
+    (CONFIG_KEYPAD == SANSA_E200_PAD)       || \
+    (CONFIG_KEYPAD == SANSA_FUZE_PAD)       || \
+    (CONFIG_KEYPAD == SANSA_FUZEPLUS_PAD)   || \
+    (CONFIG_KEYPAD == GIGABEAT_PAD)         || \
+    (CONFIG_KEYPAD == GIGABEAT_S_PAD)       || \
+    (CONFIG_KEYPAD == SAMSUNG_YH920_PAD)    || \
+    (CONFIG_KEYPAD == SAMSUNG_YH820_PAD)    || \
+    (CONFIG_KEYPAD == IAUDIO_X5M5_PAD)      || \
+    (CONFIG_KEYPAD == CREATIVE_ZEN_PAD)     || \
+    (CONFIG_KEYPAD == SONY_NWZ_PAD)         || \
+    (CONFIG_KEYPAD == CREATIVEZVM_PAD)      || \
+    (CONFIG_KEYPAD == SAMSUNG_YPR0_PAD)     || \
+    (CONFIG_KEYPAD == IRIVER_H300_PAD)      || \
+    (CONFIG_KEYPAD == HM801_PAD)
+#define BTN_UP         BUTTON_UP
+#define BTN_DOWN       BUTTON_DOWN
+#define BTN_LEFT       BUTTON_LEFT
+#define BTN_RIGHT      BUTTON_RIGHT
+
+#if (CONFIG_KEYPAD == SANSA_FUZEPLUS_PAD)
+#define BTN_UP_LEFT    BUTTON_BACK
+#define BTN_UP_RIGHT   BUTTON_PLAYPAUSE
+#define BTN_DOWN_LEFT  BUTTON_BOTTOMLEFT
+#define BTN_DOWN_RIGHT BUTTON_BOTTOMRIGHT
+#endif
+
+#if (CONFIG_KEYPAD == PHILIPS_HDD1630_PAD)    || \
+      (CONFIG_KEYPAD == PHILIPS_HDD6330_PAD)  || \
+      (CONFIG_KEYPAD == PHILIPS_SA9200_PAD)   || \
+      (CONFIG_KEYPAD == CREATIVE_ZENXFI2_PAD) || \
+      (CONFIG_KEYPAD == CREATIVE_ZENXFI3_PAD) || \
+      (CONFIG_KEYPAD == SANSA_CONNECT_PAD)    || \
+      (CONFIG_KEYPAD == SANSA_C200_PAD)       || \
+      (CONFIG_KEYPAD == SANSA_FUZEPLUS_PAD)   || \
+      (CONFIG_KEYPAD == DX50_PAD)             || \
+      (CONFIG_KEYPAD == ONDAVX747_PAD)
+#define BTN_FIRE       BUTTON_VOL_UP
+#define BTN_PAUSE      BUTTON_VOL_DOWN
+
+#elif (CONFIG_KEYPAD == SANSA_FUZE_PAD)
+#define BTN_FIRE       BUTTON_HOME
+#define BTN_PAUSE      BUTTON_SELECT
+
+#elif (CONFIG_KEYPAD == SAMSUNG_YH920_PAD)
+#define BTN_FIRE       BUTTON_FFWD
+#define BTN_PAUSE      BUTTON_REW
+
+#elif (CONFIG_KEYPAD == SANSA_E200_PAD)
+#define BTN_FIRE       BUTTON_REC
+#define BTN_PAUSE      BUTTON_POWER
+
+#elif (CONFIG_KEYPAD == SANSA_CLIP_PAD)
+#define BTN_FIRE       BUTTON_SELECT
+#define BTN_PAUSE      BUTTON_POWER
+
+#elif (CONFIG_KEYPAD == CREATIVE_ZEN_PAD)
+#define BTN_FIRE       BUTTON_SELECT
+#define BTN_PAUSE      BUTTON_BACK
+
+#elif (CONFIG_KEYPAD == CREATIVEZVM_PAD)
+#define BTN_FIRE       BUTTON_PLAY
+#define BTN_PAUSE      BUTTON_MENU
+
+#elif (CONFIG_KEYPAD == SAMSUNG_YPR0_PAD)
+#define BTN_FIRE       BUTTON_USER
+#define BTN_PAUSE      BUTTON_MENU
+
+#elif (CONFIG_KEYPAD == SONY_NWZ_PAD)
+#define BTN_FIRE       BUTTON_PLAY
+#define BTN_PAUSE      BUTTON_BACK
+
+#elif (CONFIG_KEYPAD == IRIVER_H300_PAD)
+#define BTN_FIRE       BUTTON_REC
+#define BTN_PAUSE      BUTTON_MODE
+
+#elif (CONFIG_KEYPAD == HM801_PAD)
+#define BTN_FIRE       BUTTON_PREV
+#define BTN_PAUSE      BUTTON_NEXT
+
+#elif (CONFIG_KEYPAD == SAMSUNG_YH820_PAD) || \
+      (CONFIG_KEYPAD == IAUDIO_X5M5_PAD)
+#define BTN_FIRE       BUTTON_REC
+#define BTN_PAUSE      BUTTON_PLAY
+
+#elif (CONFIG_KEYPAD == GIGABEAT_PAD)      || \
+      (CONFIG_KEYPAD == GIGABEAT_S_PAD)
+#define BTN_FIRE       BUTTON_VOL_UP
+#define BTN_PAUSE      BUTTON_MENU
+#endif
+
+#elif (CONFIG_KEYPAD == PBELL_VIBE500_PAD)
+#define BTN_UP         BUTTON_OK
+#define BTN_DOWN       BUTTON_CANCEL
+#define BTN_LEFT       BUTTON_MENU
+#define BTN_RIGHT      BUTTON_PLAY
+#define BTN_FIRE       BUTTON_POWER
+#define BTN_PAUSE      BUTTON_REC
+
+#elif (CONFIG_KEYPAD == IRIVER_H10_PAD)
+#define BTN_UP         BUTTON_SCROLL_UP
+#define BTN_DOWN       BUTTON_SCROLL_DOWN
+#define BTN_LEFT       BUTTON_LEFT
+#define BTN_RIGHT      BUTTON_RIGHT
+#define BTN_FIRE       BUTTON_REW
+#define BTN_PAUSE      BUTTON_PLAY
+
+#elif (CONFIG_KEYPAD == MROBE500_PAD)
+#define BTN_FIRE      BUTTON_POWER
+
+#elif (CONFIG_KEYPAD ==  MROBE_REMOTE)
+#define BTN_UP         BUTTON_RC_PLAY
+#define BTN_DOWN       BUTTON_RC_DOWN
+#define BTN_LEFT       BUTTON_RC_REW
+#define BTN_RIGHT      BUTTON_RC_FF
+
+#elif (CONFIG_KEYPAD == IPOD_4G_PAD) || \
+      (CONFIG_KEYPAD == IPOD_3G_PAD) || \
+      (CONFIG_KEYPAD == IPOD_1G2G_PAD)
+#define BTN_UP         BUTTON_MENU
+#define BTN_DOWN       BUTTON_PLAY
+#define BTN_LEFT       BUTTON_LEFT
+#define BTN_RIGHT      BUTTON_RIGHT
+#define BTN_FIRE       BUTTON_SELECT
+#define BTN_PAUSE      (BUTTON_MENU | BUTTON_SELECT)
+
+#elif (CONFIG_KEYPAD == ONDAVX777_PAD)
+#define BTN_FIRE       BUTTON_POWER
+
+#elif (CONFIG_KEYPAD == DX50_PAD)
+#define BTN_FIRE       BUTTON_PLUS
+#define BTN_PAUSE      BUTTON_MENU
+
+#else
+#error Unsupported keypad
+#endif
+
+#ifdef HAVE_TOUCHSCREEN
+#define BTN_UP         BUTTON_TOPMIDDLE
+#define BTN_DOWN       BUTTON_BOTTOMMIDDLE
+#define BTN_LEFT       BUTTON_LEFT
+#define BTN_RIGHT      BUTTON_RIGHT
+
+#if (CONFIG_KEYPAD == MROBE500_PAD)
+#define BTN_PAUSE       BUTTON_BOTTOMLEFT
+
+#elif (CONFIG_KEYPAD != COWON_D2_PAD) || (CONFIG_KEYPAD != DX50_PAD)
+#define BTN_FIRE       BUTTON_BOTTOMLEFT
+#define BTN_PAUSE      BUTTON_TOPLEFT
+#endif
+#endif
diff --git a/apps/plugins/xworld/mixer.c b/apps/plugins/xworld/mixer.c
new file mode 100644
index 0000000..de7536c
--- /dev/null
+++ b/apps/plugins/xworld/mixer.c
@@ -0,0 +1,199 @@
+/***************************************************************************
+ *             __________               __   ___.
+ *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
+ *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
+ *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
+ *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
+ *                     \/            \/     \/    \/            \/
+ * $Id$
+ *
+ * Copyright (C) 2014 Franklin Wei, Benjamin Brown
+ * Copyright (C) 2004 Gregory Montoir
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ***************************************************************************/
+
+#include "mixer.h"
+#include "serializer.h"
+#include "sys.h"
+
+static int8_t ICODE_ATTR addclamp(int a, int b) {
+    int add = a + b;
+    if (add < -128) {
+        add = -128;
+    }
+    else if (add > 127) {
+        add = 127;
+    }
+    return (int8_t)add;
+}
+
+void mixer_create(struct Mixer* mx, struct System *stub)
+{
+    mx->sys = stub;
+}
+
+static void mixer_mixCallback(void *param, uint8_t *buf, int len);
+
+void mixer_init(struct Mixer* mx) {
+    rb->memset(mx->_channels, 0, sizeof(mx->_channels));
+    if(!mx->sys)
+    {
+        error("in mixer sys is NULL");
+    }
+    mx->_mutex = sys_createMutex(mx->sys);
+    sys_startAudio(mx->sys, mixer_mixCallback, mx);
+}
+
+void mixer_free(struct Mixer* mx) {
+    mixer_stopAll(mx);
+    sys_stopAudio(mx->sys);
+    sys_destroyMutex(mx->sys, mx->_mutex);
+}
+
+void mixer_playChannel(struct Mixer* mx, uint8_t channel, const struct MixerChunk *mc, uint16_t freq, uint8_t volume) {
+    debug(DBG_SND, "mixer_playChannel(%d, %d, %d)", channel, freq, volume);
+    assert(channel < AUDIO_NUM_CHANNELS);
+
+    /* FW: the mutex code was converted 1:1 from C++ to C, leading to the ugly calls */
+    /*     to constructors/destructors as seen here */
+
+    struct MutexStack_t ms;
+    MutexStack(&ms, mx->sys, mx->_mutex);
+
+    struct MixerChannel *ch = &mx->_channels[channel];
+    ch->active = true;
+    ch->volume = volume;
+    ch->chunk = *mc;
+    ch->chunkPos = 0;
+    ch->chunkInc = (freq << 8) / sys_getOutputSampleRate(mx->sys);
+
+    MutexStack_destroy(&ms);
+}
+
+void mixer_stopChannel(struct Mixer* mx, uint8_t channel) {
+    debug(DBG_SND, "mixer_stopChannel(%d)", channel);
+    assert(channel < AUDIO_NUM_CHANNELS);
+
+    struct MutexStack_t ms;
+    MutexStack(&ms, mx->sys, mx->_mutex);
+
+    mx->_channels[channel].active = false;
+
+    MutexStack_destroy(&ms);
+}
+
+void mixer_setChannelVolume(struct Mixer* mx, uint8_t channel, uint8_t volume) {
+    debug(DBG_SND, "mixer_setChannelVolume(%d, %d)", channel, volume);
+    assert(channel < AUDIO_NUM_CHANNELS);
+
+    struct MutexStack_t ms;
+    MutexStack(&ms, mx->sys, mx->_mutex);
+
+    mx->_channels[channel].volume = volume;
+
+    MutexStack_destroy(&ms);
+}
+
+void mixer_stopAll(struct Mixer* mx) {
+    debug(DBG_SND, "mixer_stopAll()");
+
+    struct MutexStack_t ms;
+    MutexStack(&ms, mx->sys, mx->_mutex);
+
+    for (uint8_t i = 0; i < AUDIO_NUM_CHANNELS; ++i) {
+        mx->_channels[i].active = false;
+    }
+
+    MutexStack_destroy(&ms);
+}
+
+/* Mx is SDL callback. Called in order to populate the buf with len bytes. */
+/* The mixer iterates through all active channels and combine all sounds. */
+
+/* Since there is no way to know when SDL will ask for a buffer fill, we need */
+/* to synchronize with a mutex so the channels remain stable during the execution */
+/* of this method. */
+static void ICODE_ATTR mixer_mix(struct Mixer* mx, int8_t *buf, int len) {
+    int8_t *pBuf;
+
+    struct MutexStack_t ms;
+    MutexStack(&ms, mx->sys, mx->_mutex);
+
+    /* Clear the buffer since nothing guarantees we are receiving clean memory. */
+    rb->memset(buf, 0, len);
+
+    for (uint8_t i = 0; i < AUDIO_NUM_CHANNELS; ++i) {
+        struct MixerChannel *ch = &mx->_channels[i];
+        if (!ch->active)
+            continue;
+
+        pBuf = buf;
+        for (int j = 0; j < len; ++j, ++pBuf) {
+
+            uint16_t p1, p2;
+            uint16_t ilc = (ch->chunkPos & 0xFF);
+            p1 = ch->chunkPos >> 8;
+            ch->chunkPos += ch->chunkInc;
+
+            if (ch->chunk.loopLen != 0) {
+                if (p1 == ch->chunk.loopPos + ch->chunk.loopLen - 1) {
+                    debug(DBG_SND, "Looping sample on channel %d", i);
+                    ch->chunkPos = p2 = ch->chunk.loopPos;
+                } else {
+                    p2 = p1 + 1;
+                }
+            } else {
+                if (p1 == ch->chunk.len - 1) {
+                    debug(DBG_SND, "Stopping sample on channel %d", i);
+                    ch->active = false;
+                    break;
+                } else {
+                    p2 = p1 + 1;
+                }
+            }
+            /* interpolate */
+            int8_t b1 = *(int8_t *)(ch->chunk.data + p1);
+            int8_t b2 = *(int8_t *)(ch->chunk.data + p2);
+            int8_t b = (int8_t)((b1 * (0xFF - ilc) + b2 * ilc) >> 8);
+
+            /* set volume and clamp */
+            *pBuf = addclamp(*pBuf, (int)b * ch->volume / 0x40);  /* 0x40=64 */
+        }
+
+    }
+
+    MutexStack_destroy(&ms);
+}
+
+static void ICODE_ATTR mixer_mixCallback(void *param, uint8_t *buf, int len) {
+    debug(DBG_SND, "mixer_mixCallback");
+    mixer_mix((struct Mixer*)param, (int8_t *)buf, len);
+}
+
+void mixer_saveOrLoad(struct Mixer* mx, struct Serializer *ser) {
+    sys_lockMutex(mx->sys, mx->_mutex);
+    for (int i = 0; i < AUDIO_NUM_CHANNELS; ++i) {
+        struct MixerChannel *ch = &mx->_channels[i];
+        struct Entry entries[] = {
+            SE_INT(&ch->active, SES_BOOL, VER(2)),
+            SE_INT(&ch->volume, SES_INT8, VER(2)),
+            SE_INT(&ch->chunkPos, SES_INT32, VER(2)),
+            SE_INT(&ch->chunkInc, SES_INT32, VER(2)),
+            SE_PTR(&ch->chunk.data, VER(2)),
+            SE_INT(&ch->chunk.len, SES_INT16, VER(2)),
+            SE_INT(&ch->chunk.loopPos, SES_INT16, VER(2)),
+            SE_INT(&ch->chunk.loopLen, SES_INT16, VER(2)),
+            SE_END()
+        };
+        ser_saveOrLoadEntries(ser, entries);
+    }
+    sys_unlockMutex(mx->sys, mx->_mutex);
+};
diff --git a/apps/plugins/xworld/mixer.h b/apps/plugins/xworld/mixer.h
new file mode 100644
index 0000000..9447bcf
--- /dev/null
+++ b/apps/plugins/xworld/mixer.h
@@ -0,0 +1,69 @@
+/***************************************************************************
+ *             __________               __   ___.
+ *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
+ *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
+ *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
+ *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
+ *                     \/            \/     \/    \/            \/
+ * $Id$
+ *
+ * Copyright (C) 2014 Franklin Wei, Benjamin Brown
+ * Copyright (C) 2004 Gregory Montoir
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ***************************************************************************/
+
+#ifndef __MIXER_H__
+#define __MIXER_H__
+
+#include "intern.h"
+
+struct MixerChunk {
+    const uint8_t *data;
+    uint16_t len;
+    uint16_t loopPos;
+    uint16_t loopLen;
+};
+
+struct MixerChannel {
+    uint8_t active;
+    uint8_t volume;
+    struct MixerChunk chunk;
+    uint32_t chunkPos;
+    uint32_t chunkInc;
+};
+
+struct Serializer;
+struct System;
+
+#define AUDIO_NUM_CHANNELS 4
+
+struct Mixer {
+    void *_mutex;
+    struct System *sys;
+
+    /* Since the virtual machine and SDL are running simultaneously in two different threads */
+    /* any read or write to an elements of the sound channels MUST be synchronized with a */
+    /* mutex. */
+    struct MixerChannel _channels[AUDIO_NUM_CHANNELS];
+};
+
+void mixer_create(struct Mixer*, struct System *stub);
+void mixer_init(struct Mixer*);
+void mixer_free(struct Mixer*);
+
+void mixer_playChannel(struct Mixer*, uint8_t channel, const struct MixerChunk *mc, uint16_t freq, uint8_t volume);
+void mixer_stopChannel(struct Mixer*, uint8_t channel);
+void mixer_setChannelVolume(struct Mixer*, uint8_t channel, uint8_t volume);
+void mixer_stopAll(struct Mixer*);
+
+void mixer_saveOrLoad(struct Mixer*, struct Serializer *ser);
+
+#endif
diff --git a/apps/plugins/xworld/parts.c b/apps/plugins/xworld/parts.c
new file mode 100644
index 0000000..12b0b3b
--- /dev/null
+++ b/apps/plugins/xworld/parts.c
@@ -0,0 +1,56 @@
+/***************************************************************************
+ *             __________               __   ___.
+ *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
+ *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
+ *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
+ *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
+ *                     \/            \/     \/    \/            \/
+ * $Id$
+ *
+ * Copyright (C) 2014 Franklin Wei, Benjamin Brown
+ * Copyright (C) 2004 Gregory Montoir
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ***************************************************************************/
+
+#include "parts.h"
+
+
+/*
+  #define MEMLIST_PART_PALETTE 0
+  #define MEMLIST_PART_CODE    1
+  #define MEMLIST_PART_VIDEO1  2
+  #define MEMLIST_PART_VIDEO2  3
+*/
+
+/*
+  MEMLIST_PART_VIDEO1 and MEMLIST_PART_VIDEO2 are used to store polygons.
+
+  It seems that:
+  - MEMLIST_PART_VIDEO1 contains the cinematic polygons.
+  - MEMLIST_PART_VIDEO2 contains the polygons for player and enemies animations.
+
+  That would make sense since protection screen and cinematic game parts do not load MEMLIST_PART_VIDEO2.
+
+*/
+const uint16_t memListParts[GAME_NUM_PARTS][4] = {
+
+/* MEMLIST_PART_PALETTE   MEMLIST_PART_CODE   MEMLIST_PART_VIDEO1   MEMLIST_PART_VIDEO2 */
+    { 0x14,                    0x15,                0x16,                0x00 }, /* protection screens */
+    { 0x17,                    0x18,                0x19,                0x00 }, /* introduction cinematic */
+    { 0x1A,                    0x1B,                0x1C,                0x11 },
+    { 0x1D,                    0x1E,                0x1F,                0x11 },
+    { 0x20,                    0x21,                0x22,                0x11 },
+    { 0x23,                    0x24,                0x25,                0x00 }, /* battlechar cinematic */
+    { 0x26,                    0x27,                0x28,                0x11 },
+    { 0x29,                    0x2A,                0x2B,                0x11 },
+    { 0x7D,                    0x7E,                0x7F,                0x00 },
+    { 0x7D,                    0x7E,                0x7F,                0x00 }  /* password screen */
+};
diff --git a/apps/plugins/xworld/parts.h b/apps/plugins/xworld/parts.h
new file mode 100644
index 0000000..684306c
--- /dev/null
+++ b/apps/plugins/xworld/parts.h
@@ -0,0 +1,57 @@
+/***************************************************************************
+ *             __________               __   ___.
+ *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
+ *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
+ *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
+ *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
+ *                     \/            \/     \/    \/            \/
+ * $Id$
+ *
+ * Copyright (C) 2014 Franklin Wei, Benjamin Brown
+ * Copyright (C) 2004 Gregory Montoir
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ***************************************************************************/
+
+#ifndef __AW_PARTS_
+#define __AW_PARTS_
+
+#include "intern.h"
+#include "awendian.h"
+
+/* The game is divided in 10 parts. */
+#define GAME_NUM_PARTS 10
+
+#define GAME_PART_FIRST  0x3E80
+#define GAME_PART1       0x3E80
+#define GAME_PART2       0x3E81   /* Introduction */
+#define GAME_PART3       0x3E82
+#define GAME_PART4       0x3E83   /* Wake up in the suspended jail */
+#define GAME_PART5       0x3E84
+#define GAME_PART6       0x3E85   /* BattleChar sequence */
+#define GAME_PART7       0x3E86
+#define GAME_PART8       0x3E87
+#define GAME_PART9       0x3E88
+#define GAME_PART10      0x3E89
+#define GAME_PART_LAST   0x3E89
+
+extern const uint16_t memListParts[GAME_NUM_PARTS][4];
+
+/* For each part of the game, four resources are referenced. */
+#define MEMLIST_PART_PALETTE 0
+#define MEMLIST_PART_CODE    1
+#define MEMLIST_PART_POLY_CINEMATIC  2
+#define MEMLIST_PART_VIDEO2  3
+
+
+#define MEMLIST_PART_NONE 0x00
+
+
+#endif
diff --git a/apps/plugins/xworld/resource.c b/apps/plugins/xworld/resource.c
new file mode 100644
index 0000000..2820dcb
--- /dev/null
+++ b/apps/plugins/xworld/resource.c
@@ -0,0 +1,443 @@
+/***************************************************************************
+ *             __________               __   ___.
+ *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
+ *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
+ *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
+ *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
+ *                     \/            \/     \/    \/            \/
+ * $Id$
+ *
+ * Copyright (C) 2014 Franklin Wei, Benjamin Brown
+ * Copyright (C) 2004 Gregory Montoir
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ***************************************************************************/
+
+#include "plugin.h"
+#include "resource.h"
+#include "bank.h"
+#include "file.h"
+#include "serializer.h"
+#include "video.h"
+#include "util.h"
+#include "parts.h"
+#include "vm.h"
+#include "sys.h"
+
+void res_create(struct Resource* res, struct Video* vid, struct System* sys, const char* dataDir)
+{
+    res->video = vid;
+    res->sys = sys;
+    res->_dataDir = dataDir;
+    res->currentPartId = 0;
+    res->requestedNextPart = 0;
+}
+
+void res_readBank(struct Resource* res, const MemEntry *me, uint8_t *dstBuf) {
+    uint16_t n = me - res->_memList;
+    debug(DBG_BANK, "res_readBank(%d)", n);
+
+    struct Bank bk;
+    bank_create(&bk, res->_dataDir);
+    if (!bank_read(&bk, me, dstBuf)) {
+        error("res_readBank() unable to unpack entry %d\n", n);
+    }
+}
+
+#ifdef XWORLD_DEBUG
+static const char *resTypeToString(struct Resource* res, unsigned int type)
+{
+    (void) res;
+    static const char* resTypes[] =
+    {
+        "RT_SOUND",
+        "RT_MUSIC",
+        "RT_POLY_ANIM",
+        "RT_PALETTE",
+        "RT_BYTECODE",
+        "RT_POLY_CINEMATIC"
+    };
+    if (type >= (sizeof(resTypes) / sizeof(const char *)))
+        return "RT_UNKNOWN";
+    return resTypes[type];
+}
+#endif
+
+#define RES_SIZE 0
+#define RES_COMPRESSED 1
+int resourceSizeStats[7][2];
+#define STATS_TOTAL_SIZE 6
+int resourceUnitStats[7][2];
+
+/*
+  Read all entries from memlist.bin. Do not load anything in memory,
+  this is just a fast way to access the data later based on their id.
+*/
+void res_readEntries(struct Resource* res) {
+    File f;
+    file_create(&f, false);
+
+    int resourceCounter = 0;
+
+    if (!file_open(&f, "memlist.bin", res->_dataDir, "rb")) {
+        error("Could not open 'MEMLIST.BIN', data files missing");
+        /* error() will exit() no need to return or do anything else. */
+    }
+
+    /* Prepare stats array */
+    rb->memset(resourceSizeStats, 0, sizeof(resourceSizeStats));
+    rb->memset(resourceUnitStats, 0, sizeof(resourceUnitStats));
+
+    res->_numMemList = 0;
+    struct MemEntry *memEntry = res->_memList;
+    while (1) {
+        assert(res->_numMemList < ARRAYLEN(res->_memList));
+        memEntry->state = file_readByte(&f);
+        memEntry->type = file_readByte(&f);
+        memEntry->bufPtr = 0;
+        file_readUint16BE(&f);
+        memEntry->unk4 = file_readUint16BE(&f);
+        memEntry->rankNum = file_readByte(&f);
+        memEntry->bankId = file_readByte(&f);
+        memEntry->bankOffset = file_readUint32BE(&f);
+        memEntry->unkC = file_readUint16BE(&f);
+        memEntry->packedSize = file_readUint16BE(&f);
+        memEntry->unk10 = file_readUint16BE(&f);
+        memEntry->size = file_readUint16BE(&f);
+
+        debug(DBG_RES, "mementry state is %d", memEntry->state);
+        if (memEntry->state == MEMENTRY_STATE_END_OF_MEMLIST) {
+            break;
+        }
+
+        /* Memory tracking */
+        if (memEntry->packedSize == memEntry->size)
+        {
+            resourceUnitStats[memEntry->type][RES_SIZE] ++;
+            resourceUnitStats[STATS_TOTAL_SIZE][RES_SIZE] ++;
+        }
+        else
+        {
+            resourceUnitStats[memEntry->type][RES_COMPRESSED] ++;
+            resourceUnitStats[STATS_TOTAL_SIZE][RES_COMPRESSED] ++;
+        }
+
+        resourceSizeStats[memEntry->type][RES_SIZE] += memEntry->size;
+        resourceSizeStats[STATS_TOTAL_SIZE][RES_SIZE] += memEntry->size;
+        resourceSizeStats[memEntry->type][RES_COMPRESSED] += memEntry->packedSize;
+        resourceSizeStats[STATS_TOTAL_SIZE][RES_COMPRESSED] += memEntry->packedSize;
+
+        debug(DBG_RES, "R:0x%02X, %-17s size=%5d (compacted gain=%2.0f%%)",
+              resourceCounter,
+              resTypeToString(res, memEntry->type),
+              memEntry->size,
+              memEntry->size ? (memEntry->size - memEntry->packedSize) / (float)memEntry->size * 100.0f : 0.0f);
+
+        resourceCounter++;
+
+        res->_numMemList++;
+        memEntry++;
+    }
+
+    debug(DBG_RES, "\n");
+    debug(DBG_RES, "Total # resources: %d", resourceCounter);
+    debug(DBG_RES, "Compressed       : %d", resourceUnitStats[STATS_TOTAL_SIZE][RES_COMPRESSED]);
+    debug(DBG_RES, "Uncompressed     : %d", resourceUnitStats[STATS_TOTAL_SIZE][RES_SIZE]);
+    debug(DBG_RES, "Note: %2.0f%% of resources are compressed.", 100 * resourceUnitStats[STATS_TOTAL_SIZE][RES_COMPRESSED] / (float)resourceCounter);
+    debug(DBG_RES, "\n");
+    debug(DBG_RES, "Total size (uncompressed) : %7d bytes.", resourceSizeStats[STATS_TOTAL_SIZE][RES_SIZE]);
+    debug(DBG_RES, "Total size (compressed)   : %7d bytes.", resourceSizeStats[STATS_TOTAL_SIZE][RES_COMPRESSED]);
+    debug(DBG_RES, "Note: Overall compression gain is : %2.0f%%.",
+          (resourceSizeStats[STATS_TOTAL_SIZE][RES_SIZE] - resourceSizeStats[STATS_TOTAL_SIZE][RES_COMPRESSED]) / (float)resourceSizeStats[STATS_TOTAL_SIZE][RES_SIZE] * 100);
+
+    debug(DBG_RES, "\n");
+    for(int i = 0 ; i < 6 ; i++)
+        debug(DBG_RES, "Total %-17s unpacked size: %7d (%2.0f%% of total unpacked size) packedSize %7d (%2.0f%% of floppy space) gain:(%2.0f%%)",
+              resTypeToString(res, i),
+              resourceSizeStats[i][RES_SIZE],
+              resourceSizeStats[i][RES_SIZE] / (float)resourceSizeStats[STATS_TOTAL_SIZE][RES_SIZE] * 100.0f,
+              resourceSizeStats[i][RES_COMPRESSED],
+              resourceSizeStats[i][RES_COMPRESSED] / (float)resourceSizeStats[STATS_TOTAL_SIZE][RES_COMPRESSED] * 100.0f,
+              (resourceSizeStats[i][RES_SIZE] - resourceSizeStats[i][RES_COMPRESSED]) / (float)resourceSizeStats[i][RES_SIZE] * 100.0f);
+
+    debug(DBG_RES, "Note: Damn you sound compression rate!");
+
+    debug(DBG_RES, "\nTotal bank files:              %d", resourceUnitStats[STATS_TOTAL_SIZE][RES_SIZE] + resourceUnitStats[STATS_TOTAL_SIZE][RES_COMPRESSED]);
+    for(int i = 0 ; i < 6 ; i++)
+        debug(DBG_RES, "Total %-17s files: %3d", resTypeToString(res, i), resourceUnitStats[i][RES_SIZE] + resourceUnitStats[i][RES_COMPRESSED]);
+
+    file_close(&f);
+}
+
+/*
+  Go over every resource and check if they are marked at "MEMENTRY_STATE_LOAD_ME".
+  Load them in memory and mark them are MEMENTRY_STATE_LOADED
+*/
+void res_loadMarkedAsNeeded(struct Resource* res) {
+
+    while (1) {
+        struct MemEntry *me = NULL;
+
+        /* get resource with max rankNum */
+        uint8_t maxNum = 0;
+        uint16_t i = res->_numMemList;
+        struct MemEntry *it = res->_memList;
+        while (i--) {
+            if (it->state == MEMENTRY_STATE_LOAD_ME && maxNum <= it->rankNum) {
+                maxNum = it->rankNum;
+                me = it;
+            }
+            it++;
+        }
+
+        if (me == NULL) {
+            break; // no entry found
+        }
+
+
+        /* At this point the resource descriptor should be pointed to "me" */
+
+        uint8_t *loadDestination = NULL;
+        if (me->type == RT_POLY_ANIM) {
+            loadDestination = res->_vidCurPtr;
+        } else {
+            loadDestination = res->_scriptCurPtr;
+            if (me->size > res->_vidBakPtr - res->_scriptCurPtr) {
+                warning("res_load() not enough memory");
+                me->state = MEMENTRY_STATE_NOT_NEEDED;
+                continue;
+            }
+        }
+
+
+        if (me->bankId == 0) {
+            warning("res_load() ec=0x%X (me->bankId == 0)", 0xF00);
+            me->state = MEMENTRY_STATE_NOT_NEEDED;
+        } else {
+            debug(DBG_BANK, "res_load() bufPos=%X size=%X type=%X pos=%X bankId=%X", loadDestination - res->_memPtrStart, me->packedSize, me->type, me->bankOffset, me->bankId);
+            res_readBank(res, me, loadDestination);
+            if(me->type == RT_POLY_ANIM) {
+                video_copyPagePtr(res->video, res->_vidCurPtr);
+                me->state = MEMENTRY_STATE_NOT_NEEDED;
+            } else {
+                me->bufPtr = loadDestination;
+                me->state = MEMENTRY_STATE_LOADED;
+                res->_scriptCurPtr += me->size;
+            }
+        }
+    }
+}
+
+void res_invalidateRes(struct Resource* res) {
+    struct MemEntry *me = res->_memList;
+    uint16_t i = res->_numMemList;
+    while (i--) {
+        if (me->type <= RT_POLY_ANIM || me->type > 6) {  /* 6 WTF ?!?! ResType goes up to 5 !! */
+            me->state = MEMENTRY_STATE_NOT_NEEDED;
+        }
+        ++me;
+    }
+    res->_scriptCurPtr = res->_scriptBakPtr;
+}
+
+void res_invalidateAll(struct Resource* res) {
+    struct MemEntry *me = res->_memList;
+    uint16_t i = res->_numMemList;
+    while (i--) {
+        me->state = MEMENTRY_STATE_NOT_NEEDED;
+        ++me;
+    }
+    res->_scriptCurPtr = res->_memPtrStart;
+}
+
+/* This method serves two purpose:
+   - Load parts in memory segments (palette,code,video1,video2)
+   or
+   - Load a resource in memory
+
+   This is decided based on the resourceId. If it does not match a mementry id it is supposed to
+   be a part id. */
+void res_loadPartsOrMemoryEntry(struct Resource* res, uint16_t resourceId) {
+
+    if (resourceId > res->_numMemList) {
+
+        res->requestedNextPart = resourceId;
+
+    } else {
+
+        struct MemEntry *me = &res->_memList[resourceId];
+
+        if (me->state == MEMENTRY_STATE_NOT_NEEDED) {
+            me->state = MEMENTRY_STATE_LOAD_ME;
+            res_loadMarkedAsNeeded(res);
+        }
+    }
+
+}
+
+/* Protection screen and cinematic don't need the player and enemies polygon data
+   so _memList[video2Index] is never loaded for those parts of the game. When
+   needed (for action phrases) _memList[video2Index] is always loaded with 0x11
+   (as seen in memListParts). */
+void res_setupPart(struct Resource* res, uint16_t partId) {
+
+    if (partId == res->currentPartId)
+        return;
+
+    if (partId < GAME_PART_FIRST || partId > GAME_PART_LAST)
+        error("res_setupPart() ec=0x%X invalid partId", partId);
+
+    uint16_t memListPartIndex = partId - GAME_PART_FIRST;
+
+    uint8_t paletteIndex = memListParts[memListPartIndex][MEMLIST_PART_PALETTE];
+    uint8_t codeIndex    = memListParts[memListPartIndex][MEMLIST_PART_CODE];
+    uint8_t videoCinematicIndex  = memListParts[memListPartIndex][MEMLIST_PART_POLY_CINEMATIC];
+    uint8_t video2Index  = memListParts[memListPartIndex][MEMLIST_PART_VIDEO2];
+
+    /* Mark all resources as located on harddrive. */
+    res_invalidateAll(res);
+
+    res->_memList[paletteIndex].state = MEMENTRY_STATE_LOAD_ME;
+    res->_memList[codeIndex].state = MEMENTRY_STATE_LOAD_ME;
+    res->_memList[videoCinematicIndex].state = MEMENTRY_STATE_LOAD_ME;
+
+    /* This is probably a cinematic or a non interactive part of the game. */
+    /* Player and enemy polygons are not needed. */
+    if (video2Index != MEMLIST_PART_NONE)
+        res->_memList[video2Index].state = MEMENTRY_STATE_LOAD_ME;
+
+
+    res_loadMarkedAsNeeded(res);
+
+    res->segPalettes = res->_memList[paletteIndex].bufPtr;
+    debug(DBG_RES, "paletteIndex is 0x%08x", res->segPalettes);
+    res->segBytecode     = res->_memList[codeIndex].bufPtr;
+    res->segCinematic   = res->_memList[videoCinematicIndex].bufPtr;
+
+
+
+    /* This is probably a cinematic or a non interactive part of the game. */
+    /* Player and enemy polygons are not needed. */
+    if (video2Index != MEMLIST_PART_NONE)
+        res->_segVideo2 = res->_memList[video2Index].bufPtr;
+
+    debug(DBG_RES, "");
+    debug(DBG_RES, "setupPart(%d)", partId - GAME_PART_FIRST);
+    debug(DBG_RES, "Loaded resource %d (%s) in segPalettes.", paletteIndex, resTypeToString(res, res->_memList[paletteIndex].type));
+    debug(DBG_RES, "Loaded resource %d (%s) in segBytecode.", codeIndex, resTypeToString(res, res->_memList[codeIndex].type));
+    debug(DBG_RES, "Loaded resource %d (%s) in segCinematic.", videoCinematicIndex, resTypeToString(res, res->_memList[videoCinematicIndex].type));
+
+
+    /* prevent warnings: */
+#ifdef XWORLD_DEBUG
+    if (video2Index != MEMLIST_PART_NONE)
+        debug(DBG_RES, "Loaded resource %d (%s) in _segVideo2.", video2Index, resTypeToString(res, res->_memList[video2Index].type));
+#endif
+
+
+    res->currentPartId = partId;
+
+
+    /* _scriptCurPtr is changed in res->load(); */
+    res->_scriptBakPtr = res->_scriptCurPtr;
+}
+
+void res_allocMemBlock(struct Resource* res) {
+    if(rb->audio_status())
+        rb->audio_stop();
+    /* steal the audio buffer */
+    size_t sz;
+    /* memory usage is as follows:
+       [VM memory - 600K]
+       [Framebuffers - 128K]
+       [Temporary framebuffer - 192K]
+       [String table buffer]
+    */
+    res->_memPtrStart = rb->plugin_get_audio_buffer(&sz);
+    if(sz < MEM_BLOCK_SIZE + (4 * VID_PAGE_SIZE) + 320 * 200 * sizeof(fb_data))
+    {
+        warning("res_allocMemBlock: can't allocate enough memory!");
+    }
+
+    res->sys->membuf = res->_memPtrStart + ( MEM_BLOCK_SIZE + (4 * VID_PAGE_SIZE) + 320 * 200 * sizeof(fb_data));
+    res->sys->bytes_left = sz - (MEM_BLOCK_SIZE + (4 * VID_PAGE_SIZE) + 320 * 200 * sizeof(fb_data));
+
+    debug(DBG_RES, "audiobuf is %d bytes in size", sz);
+
+    res->_scriptBakPtr = res->_scriptCurPtr = res->_memPtrStart;
+    res->_vidBakPtr = res->_vidCurPtr = res->_memPtrStart + MEM_BLOCK_SIZE - 0x800 * 16; //0x800 = 2048, so we have 32KB free for vidBack and vidCur
+    res->_useSegVideo2 = false;
+}
+
+void res_freeMemBlock(struct Resource* res) {
+    (void) res;
+    /* there's no need to do anything to free the audio buffer */
+    return;
+}
+
+void res_saveOrLoad(struct Resource* res, struct Serializer *ser) {
+    uint8_t loadedList[64];
+    if (ser->_mode == SM_SAVE) {
+        rb->memset(loadedList, 0, sizeof(loadedList));
+        uint8_t *p = loadedList;
+        uint8_t *q = res->_memPtrStart;
+        while (1) {
+            struct MemEntry *it = res->_memList;
+            struct MemEntry *me = 0;
+            uint16_t num = res->_numMemList;
+            while (num--) {
+                if (it->state == MEMENTRY_STATE_LOADED && it->bufPtr == q) {
+                    me = it;
+                }
+                ++it;
+            }
+            if (me == 0) {
+                break;
+            } else {
+                assert(p < loadedList + 64);
+                *p++ = me - res->_memList;
+                q += me->size;
+            }
+        }
+    }
+
+    struct Entry entries[] = {
+        SE_ARRAY(loadedList, 64, SES_INT8, VER(1)),
+        SE_INT(&res->currentPartId, SES_INT16, VER(1)),
+        SE_PTR(&res->_scriptBakPtr, VER(1)),
+        SE_PTR(&res->_scriptCurPtr, VER(1)),
+        SE_PTR(&res->_vidBakPtr, VER(1)),
+        SE_PTR(&res->_vidCurPtr, VER(1)),
+        SE_INT(&res->_useSegVideo2, SES_BOOL, VER(1)),
+        SE_PTR(&res->segPalettes, VER(1)),
+        SE_PTR(&res->segBytecode, VER(1)),
+        SE_PTR(&res->segCinematic, VER(1)),
+        SE_PTR(&res->_segVideo2, VER(1)),
+        SE_END()
+    };
+
+    ser_saveOrLoadEntries(ser, entries);
+    if (ser->_mode == SM_LOAD) {
+        uint8_t *p = loadedList;
+        uint8_t *q = res->_memPtrStart;
+        while (*p) {
+            struct MemEntry *me = &res->_memList[*p++];
+            res_readBank(res, me, q);
+            me->bufPtr = q;
+            me->state = MEMENTRY_STATE_LOADED;
+            q += me->size;
+        }
+    }
+}
+
+const char* res_getDataDir(struct Resource* res)
+{
+    return res->_dataDir;
+}
diff --git a/apps/plugins/xworld/resource.h b/apps/plugins/xworld/resource.h
new file mode 100644
index 0000000..3242777
--- /dev/null
+++ b/apps/plugins/xworld/resource.h
@@ -0,0 +1,107 @@
+/***************************************************************************
+ *             __________               __   ___.
+ *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
+ *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
+ *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
+ *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
+ *                     \/            \/     \/    \/            \/
+ * $Id$
+ *
+ * Copyright (C) 2014 Franklin Wei, Benjamin Brown
+ * Copyright (C) 2004 Gregory Montoir
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ***************************************************************************/
+
+#ifndef __RESOURCE_H__
+#define __RESOURCE_H__
+
+#include "intern.h"
+
+
+#define MEMENTRY_STATE_END_OF_MEMLIST 0xFF
+#define MEMENTRY_STATE_NOT_NEEDED 0
+#define MEMENTRY_STATE_LOADED 1
+#define MEMENTRY_STATE_LOAD_ME 2
+
+/*
+  This is a directory entry. When the game starts, it loads memlist.bin and
+  populate and array of MemEntry
+*/
+typedef struct MemEntry {
+    uint8_t state;         /* 0x0 */
+    uint8_t type;          /* 0x1, Resource::ResType */
+    uint8_t *bufPtr;       /* 0x2 */
+    uint16_t unk4;         /* 0x4, unused */
+    uint8_t rankNum;       /* 0x6 */
+    uint8_t bankId;        /* 0x7 */
+    uint32_t bankOffset;   /* 0x8 0xA */
+    uint16_t unkC;         /* 0xC, unused */
+    uint16_t packedSize;   /* 0xE */
+    /* All ressources are packed (for a gain of 28% according to Chahi) */
+
+    uint16_t unk10;        /* 0x10, unused */
+    uint16_t size;         /* 0x12 */
+} __attribute__((packed)) MemEntry;
+
+/*
+  Note: state is not a boolean, it can have value 0, 1, 2 or 255, respectively meaning:
+  0:NOT_NEEDED
+  1:LOADED
+  2:LOAD_ME
+  255:END_OF_MEMLIST
+
+  See MEMENTRY_STATE_* #defines above.
+*/
+
+struct Serializer;
+struct Video;
+
+#define MEM_BLOCK_SIZE  (600 * 1024)
+#define RT_SOUND 0
+#define RT_MUSIC 1
+#define RT_POLY_ANIM 2
+#define RT_PALETTE 3
+#define RT_BYTECODE 4
+#define RT_POLY_CINEMATIC 5
+
+struct Resource {
+    struct Video *video;
+    struct System *sys;
+    const char *_dataDir;
+    struct MemEntry _memList[150];
+    uint16_t _numMemList;
+    uint16_t currentPartId, requestedNextPart;
+    uint8_t *_memPtrStart, *_scriptBakPtr, *_scriptCurPtr, *_vidBakPtr, *_vidCurPtr;
+    bool _useSegVideo2;
+
+    uint8_t *segPalettes;
+    uint8_t *segBytecode;
+    uint8_t *segCinematic;
+    uint8_t *_segVideo2;
+};
+
+
+void res_create(struct Resource*, struct Video*, struct System*, const char* dataDir);
+
+void res_readBank(struct Resource*, const MemEntry *me, uint8_t *dstBuf);
+void res_readEntries(struct Resource*);
+void res_loadMarkedAsNeeded(struct Resource*);
+void res_invalidateAll(struct Resource*);
+void res_invalidateRes(struct Resource*);
+void res_loadPartsOrMemoryEntry(struct Resource*, uint16_t num);
+void res_setupPart(struct Resource*, uint16_t ptrId);
+void res_allocMemBlock(struct Resource*);
+void res_freeMemBlock(struct Resource*);
+
+void res_saveOrLoad(struct Resource*, struct Serializer *ser);
+
+const char* res_getDataDir(struct Resource*);
+#endif
diff --git a/apps/plugins/xworld/serializer.c b/apps/plugins/xworld/serializer.c
new file mode 100644
index 0000000..5268c27
--- /dev/null
+++ b/apps/plugins/xworld/serializer.c
@@ -0,0 +1,141 @@
+/***************************************************************************
+ *             __________               __   ___.
+ *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
+ *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
+ *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
+ *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
+ *                     \/            \/     \/    \/            \/
+ * $Id$
+ *
+ * Copyright (C) 2014 Franklin Wei, Benjamin Brown
+ * Copyright (C) 2004 Gregory Montoir
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ***************************************************************************/
+
+#include "serializer.h"
+#include "file.h"
+
+
+void ser_create(struct Serializer* c, File *stream, enum Mode mode, uint8_t *ptrBlock, uint16_t saveVer)
+{
+    c->_stream = stream;
+    c->_mode = mode;
+    c->_ptrBlock = ptrBlock;
+    c->_saveVer = saveVer;
+}
+
+void ser_saveOrLoadEntries(struct Serializer* c, struct Entry *entry) {
+    debug(DBG_SER, "ser_saveOrLoadEntries() _mode=%d", c->_mode);
+    c->_bytesCount = 0;
+    switch (c->_mode) {
+    case SM_SAVE:
+        ser_saveEntries(c, entry);
+        break;
+    case SM_LOAD:
+        ser_loadEntries(c, entry);
+        break;
+    }
+    debug(DBG_SER, "ser_saveOrLoadEntries() _bytesCount=%d", c->_bytesCount);
+}
+
+void ser_saveEntries(struct Serializer* c, struct Entry *entry) {
+    debug(DBG_SER, "ser_saveEntries()");
+    for (; entry->type != SET_END; ++entry) {
+        if (entry->maxVer == CUR_VER) {
+            switch (entry->type) {
+            case SET_INT:
+                ser_saveInt(c, entry->size, entry->data);
+                c->_bytesCount += entry->size;
+                break;
+            case SET_ARRAY:
+                if (entry->size == SES_INT8) {
+                    file_write(c->_stream, entry->data, entry->n);
+                    c->_bytesCount += entry->n;
+                } else {
+                    uint8_t *p = (uint8_t *)entry->data;
+                    for (int i = 0; i < entry->n; ++i) {
+                        ser_saveInt(c, entry->size, p);
+                        p += entry->size;
+                        c->_bytesCount += entry->size;
+                    }
+                }
+                break;
+            case SET_PTR:
+                file_writeUint32BE(c->_stream, *(uint8_t **)(entry->data) - c->_ptrBlock);
+                c->_bytesCount += 4;
+                break;
+            case SET_END:
+                break;
+            }
+        }
+    }
+}
+
+void ser_loadEntries(struct Serializer* c, struct Entry *entry) {
+    debug(DBG_SER, "ser_loadEntries()");
+    for (; entry->type != SET_END; ++entry) {
+        if (c->_saveVer >= entry->minVer && c->_saveVer <= entry->maxVer) {
+            switch (entry->type) {
+            case SET_INT:
+                ser_loadInt(c, entry->size, entry->data);
+                c->_bytesCount += entry->size;
+                break;
+            case SET_ARRAY:
+                if (entry->size == SES_INT8) {
+                    file_read(c->_stream, entry->data, entry->n);
+                    c->_bytesCount += entry->n;
+                } else {
+                    uint8_t *p = (uint8_t *)entry->data;
+                    for (int i = 0; i < entry->n; ++i) {
+                        ser_loadInt(c, entry->size, p);
+                        p += entry->size;
+                        c->_bytesCount += entry->size;
+                    }
+                }
+                break;
+            case SET_PTR:
+                *(uint8_t **)(entry->data) = c->_ptrBlock + file_readUint32BE(c->_stream);
+                c->_bytesCount += 4;
+                break;
+            case SET_END:
+                break;
+            }
+        }
+    }
+}
+
+void ser_saveInt(struct Serializer* c, uint8_t es, void *p) {
+    switch (es) {
+    case 1:
+        file_writeByte(c->_stream, *(uint8_t *)p);
+        break;
+    case 2:
+        file_writeUint16BE(c->_stream, *(uint16_t *)p);
+        break;
+    case 4:
+        file_writeUint32BE(c->_stream, *(uint32_t *)p);
+        break;
+    }
+}
+
+void ser_loadInt(struct Serializer* c, uint8_t es, void *p) {
+    switch (es) {
+    case 1:
+        *(uint8_t *)p = file_readByte(c->_stream);
+        break;
+    case 2:
+        *(uint16_t *)p = file_readUint16BE(c->_stream);
+        break;
+    case 4:
+        *(uint32_t *)p = file_readUint32BE(c->_stream);
+        break;
+    }
+}
diff --git a/apps/plugins/xworld/serializer.h b/apps/plugins/xworld/serializer.h
new file mode 100644
index 0000000..fde8f6d
--- /dev/null
+++ b/apps/plugins/xworld/serializer.h
@@ -0,0 +1,84 @@
+/***************************************************************************
+ *             __________               __   ___.
+ *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
+ *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
+ *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
+ *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
+ *                     \/            \/     \/    \/            \/
+ * $Id$
+ *
+ * Copyright (C) 2014 Franklin Wei, Benjamin Brown
+ * Copyright (C) 2004 Gregory Montoir
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ***************************************************************************/
+
+#ifndef __SERIALIZER_H__
+#define __SERIALIZER_H__
+
+#include "intern.h"
+
+#define CUR_VER 2
+
+#define VER(x) x
+
+enum EntryType {
+    SET_INT,
+    SET_ARRAY,
+    SET_PTR,
+    SET_END
+};
+
+#define SE_INT(i,sz,ver)     { SET_INT, sz, 1, i, ver, CUR_VER }
+#define SE_ARRAY(a,n,sz,ver) { SET_ARRAY, sz, n, a, ver, CUR_VER }
+#define SE_PTR(p,ver)        { SET_PTR, 0, 0, p, ver, CUR_VER }
+#define SE_END()             { SET_END, 0, 0, 0, 0, 0 }
+
+struct File;
+
+enum {
+    SES_BOOL  = 1,
+    SES_INT8  = 1,
+    SES_INT16 = 2,
+    SES_INT32 = 4
+};
+
+enum Mode {
+    SM_SAVE,
+    SM_LOAD
+};
+
+struct Entry {
+    enum EntryType type;
+    uint8_t size;
+    uint16_t n;
+    void *data;
+    uint16_t minVer;
+    uint16_t maxVer;
+};
+
+struct Serializer {
+    File *_stream;
+    enum Mode _mode;
+    uint8_t *_ptrBlock;
+    uint16_t _saveVer;
+    uint32_t _bytesCount;
+};
+
+void ser_create(struct Serializer*, File *stream, enum Mode mode, uint8_t *ptrBlock, uint16_t saveVer);
+
+void ser_saveOrLoadEntries(struct Serializer*, struct Entry *entry);
+
+void ser_saveEntries(struct Serializer*, struct Entry *entry);
+void ser_loadEntries(struct Serializer*, struct Entry *entry);
+
+void ser_saveInt(struct Serializer*, uint8_t es, void *p);
+void ser_loadInt(struct Serializer*, uint8_t es, void *p);
+#endif
diff --git a/apps/plugins/xworld/sfxplayer.c b/apps/plugins/xworld/sfxplayer.c
new file mode 100644
index 0000000..9bdc143
--- /dev/null
+++ b/apps/plugins/xworld/sfxplayer.c
@@ -0,0 +1,247 @@
+/***************************************************************************
+ *             __________               __   ___.
+ *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
+ *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
+ *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
+ *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
+ *                     \/            \/     \/    \/            \/
+ * $Id$
+ *
+ * Copyright (C) 2014 Franklin Wei, Benjamin Brown
+ * Copyright (C) 2004 Gregory Montoir
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ***************************************************************************/
+
+#include "sfxplayer.h"
+#include "mixer.h"
+#include "resource.h"
+#include "serializer.h"
+#include "sys.h"
+
+void player_create(struct SfxPlayer* sfx, struct Mixer *mix, struct Resource *res, struct System *stub)
+{
+    sfx->mixer = mix;
+    sfx->res = res;
+    sfx->sys = stub;
+    sfx->_delay = 0;
+    sfx->_resNum = 0;
+}
+
+void player_init(struct SfxPlayer* sfx) {
+    debug(DBG_SND, "sys is 0x%08x", sfx->sys);
+    sfx->_mutex = sys_createMutex(sfx->sys);
+}
+
+void player_free(struct SfxPlayer* sfx) {
+    player_stop(sfx);
+    sys_destroyMutex(sfx->sys, sfx->_mutex);
+}
+
+void player_setEventsDelay(struct SfxPlayer* sfx, uint16_t delay) {
+    debug(DBG_SND, "player_setEventsDelay(%d)", delay);
+    struct MutexStack_t ms;
+    MutexStack(&ms, sfx->sys, sfx->_mutex);
+    sfx->_delay = delay * 60 / 7050;
+    MutexStack_destroy(&ms);
+}
+
+void player_loadSfxModule(struct SfxPlayer* sfx, uint16_t resNum, uint16_t delay, uint8_t pos) {
+
+    debug(DBG_SND, "player_loadSfxModule(0x%X, %d, %d)", resNum, delay, pos);
+    struct MutexStack_t ms;
+    MutexStack(&ms, sfx->sys, sfx->_mutex);
+
+
+    struct MemEntry *me = &sfx->res->_memList[resNum];
+
+    if (me->state == MEMENTRY_STATE_LOADED && me->type == RT_MUSIC) {
+        sfx->_resNum = resNum;
+        rb->memset(&sfx->_sfxMod, 0, sizeof(struct SfxModule));
+        sfx->_sfxMod.curOrder = pos;
+        sfx->_sfxMod.numOrder = READ_BE_UINT16(me->bufPtr + 0x3E);
+        debug(DBG_SND, "player_loadSfxModule() curOrder = 0x%X numOrder = 0x%X", sfx->_sfxMod.curOrder, sfx->_sfxMod.numOrder);
+        for (int i = 0; i < 0x80; ++i) {
+            sfx->_sfxMod.orderTable[i] = *(me->bufPtr + 0x40 + i);
+        }
+        if (delay == 0) {
+            sfx->_delay = READ_BE_UINT16(me->bufPtr);
+        } else {
+            sfx->_delay = delay;
+        }
+        sfx->_delay = sfx->_delay * 60 / 7050;
+        sfx->_sfxMod.data = me->bufPtr + 0xC0;
+        debug(DBG_SND, "player_loadSfxModule() eventDelay = %d ms", sfx->_delay);
+        player_prepareInstruments(sfx, me->bufPtr + 2);
+    } else {
+        warning("player_loadSfxModule() ec=0x%X", 0xF8);
+    }
+    MutexStack_destroy(&ms);
+}
+
+void player_prepareInstruments(struct SfxPlayer* sfx, const uint8_t *p) {
+
+    rb->memset(sfx->_sfxMod.samples, 0, sizeof(sfx->_sfxMod.samples));
+
+    for (int i = 0; i < 15; ++i) {
+        struct SfxInstrument *ins = &sfx->_sfxMod.samples[i];
+        uint16_t resNum = READ_BE_UINT16(p);
+        p += 2;
+        if (resNum != 0) {
+            ins->volume = READ_BE_UINT16(p);
+            struct MemEntry *me = &sfx->res->_memList[resNum];
+            if (me->state == MEMENTRY_STATE_LOADED && me->type == RT_SOUND) {
+                ins->data = me->bufPtr;
+                rb->memset(ins->data + 8, 0, 4);
+                debug(DBG_SND, "Loaded instrument 0x%X n=%d volume=%d", resNum, i, ins->volume);
+            } else {
+                error("Error loading instrument 0x%X", resNum);
+            }
+        }
+        p += 2; /* skip volume */
+    }
+}
+
+void player_start(struct SfxPlayer* sfx) {
+    debug(DBG_SND, "player_start()");
+    struct MutexStack_t ms;
+    MutexStack(&ms, sfx->sys, sfx->_mutex);
+    sfx->_sfxMod.curPos = 0;
+    sfx->_timerId = sys_addTimer(sfx->sys, sfx->_delay, player_eventsCallback, sfx);
+    MutexStack_destroy(&ms);
+}
+
+void player_stop(struct SfxPlayer* sfx) {
+    debug(DBG_SND, "player_stop()");
+    struct MutexStack_t ms;
+    MutexStack(&ms, sfx->sys, sfx->_mutex);
+    if (sfx->_resNum != 0) {
+        sfx->_resNum = 0;
+        sys_removeTimer(sfx->sys, sfx->_timerId);
+    }
+    MutexStack_destroy(&ms);
+}
+
+void player_handleEvents(struct SfxPlayer* sfx) {
+    struct MutexStack_t ms;
+    MutexStack(&ms, sfx->sys, sfx->_mutex);
+    uint8_t order = sfx->_sfxMod.orderTable[sfx->_sfxMod.curOrder];
+    const uint8_t *patternData = sfx->_sfxMod.data + sfx->_sfxMod.curPos + order * 1024;
+    for (uint8_t ch = 0; ch < 4; ++ch) {
+        player_handlePattern(sfx, ch, patternData);
+        patternData += 4;
+    }
+    sfx->_sfxMod.curPos += 4 * 4;
+    debug(DBG_SND, "player_handleEvents() order = 0x%X curPos = 0x%X", order, sfx->_sfxMod.curPos);
+    if (sfx->_sfxMod.curPos >= 1024) {
+        sfx->_sfxMod.curPos = 0;
+        order = sfx->_sfxMod.curOrder + 1;
+        if (order == sfx->_sfxMod.numOrder) {
+            sfx->_resNum = 0;
+            sys_removeTimer(sfx->sys, sfx->_timerId);
+            mixer_stopAll(sfx->mixer);
+        }
+        sfx->_sfxMod.curOrder = order;
+    }
+    MutexStack_destroy(&ms);
+}
+
+void player_handlePattern(struct SfxPlayer* sfx, uint8_t channel, const uint8_t *data) {
+    struct SfxPattern pat;
+    rb->memset(&pat, 0, sizeof(struct SfxPattern));
+    pat.note_1 = READ_BE_UINT16(data + 0);
+    pat.note_2 = READ_BE_UINT16(data + 2);
+    if (pat.note_1 != 0xFFFD) {
+        uint16_t sample = (pat.note_2 & 0xF000) >> 12;
+        if (sample != 0) {
+            uint8_t *ptr = sfx->_sfxMod.samples[sample - 1].data;
+            if (ptr != 0) {
+                debug(DBG_SND, "player_handlePattern() preparing sample %d", sample);
+                pat.sampleVolume = sfx->_sfxMod.samples[sample - 1].volume;
+                pat.sampleStart = 8;
+                pat.sampleBuffer = ptr;
+                pat.sampleLen = READ_BE_UINT16(ptr) * 2;
+                uint16_t loopLen = READ_BE_UINT16(ptr + 2) * 2;
+                if (loopLen != 0) {
+                    pat.loopPos = pat.sampleLen;
+                    pat.loopData = ptr;
+                    pat.loopLen = loopLen;
+                } else {
+                    pat.loopPos = 0;
+                    pat.loopData = 0;
+                    pat.loopLen = 0;
+                }
+                int16_t m = pat.sampleVolume;
+                uint8_t effect = (pat.note_2 & 0x0F00) >> 8;
+                if (effect == 5) { /* volume up */
+                    uint8_t volume = (pat.note_2 & 0xFF);
+                    m += volume;
+                    if (m > 0x3F) {
+                        m = 0x3F;
+                    }
+                } else if (effect == 6) { /* volume down */
+                    uint8_t volume = (pat.note_2 & 0xFF);
+                    m -= volume;
+                    if (m < 0) {
+                        m = 0;
+                    }
+                }
+                mixer_setChannelVolume(sfx->mixer, channel, m);
+                pat.sampleVolume = m;
+            }
+        }
+    }
+    if (pat.note_1 == 0xFFFD) {
+        debug(DBG_SND, "player_handlePattern() _scriptVars[0xF4] = 0x%X", pat.note_2);
+        *sfx->_markVar = pat.note_2;
+    } else if (pat.note_1 != 0) {
+        if (pat.note_1 == 0xFFFE) {
+            mixer_stopChannel(sfx->mixer, channel);
+        } else if (pat.sampleBuffer != 0) {
+            struct MixerChunk mc;
+            rb->memset(&mc, 0, sizeof(mc));
+            mc.data = pat.sampleBuffer + pat.sampleStart;
+            mc.len = pat.sampleLen;
+            mc.loopPos = pat.loopPos;
+            mc.loopLen = pat.loopLen;
+            /* convert amiga period value to hz */
+            uint16_t freq = 7159092 / (pat.note_1 * 2);
+            debug(DBG_SND, "player_handlePattern() adding sample freq = 0x%X", freq);
+            mixer_playChannel(sfx->mixer, channel, &mc, freq, pat.sampleVolume);
+        }
+    }
+}
+
+uint32_t player_eventsCallback(uint32_t interval, void *param) {
+    (void) interval;
+    debug(DBG_SND, "player_eventsCallback with interval %d ms and param 0x%08x", interval, param);
+    struct SfxPlayer *p = (struct SfxPlayer *)param;
+    player_handleEvents(p);
+    return p->_delay;
+}
+
+void player_saveOrLoad(struct SfxPlayer* sfx, struct Serializer *ser) {
+    sys_lockMutex(sfx->sys, sfx->_mutex);
+    struct Entry entries[] = {
+        SE_INT(&sfx->_delay, SES_INT8, VER(2)),
+        SE_INT(&sfx->_resNum, SES_INT16, VER(2)),
+        SE_INT(&sfx->_sfxMod.curPos, SES_INT16, VER(2)),
+        SE_INT(&sfx->_sfxMod.curOrder, SES_INT8, VER(2)),
+        SE_END()
+    };
+    ser_saveOrLoadEntries(ser, entries);
+    sys_unlockMutex(sfx->sys, sfx->_mutex);
+    if (ser->_mode == SM_LOAD && sfx->_resNum != 0) {
+        uint16_t delay = sfx->_delay;
+        player_loadSfxModule(sfx, sfx->_resNum, 0, sfx->_sfxMod.curOrder);
+        sfx->_delay = delay;
+        sfx->_timerId = sys_addTimer(sfx->sys, sfx->_delay, player_eventsCallback, sfx);
+    }
+}
diff --git a/apps/plugins/xworld/sfxplayer.h b/apps/plugins/xworld/sfxplayer.h
new file mode 100644
index 0000000..0bf92b9
--- /dev/null
+++ b/apps/plugins/xworld/sfxplayer.h
@@ -0,0 +1,88 @@
+/***************************************************************************
+ *             __________               __   ___.
+ *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
+ *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
+ *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
+ *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
+ *                     \/            \/     \/    \/            \/
+ * $Id$
+ *
+ * Copyright (C) 2014 Franklin Wei, Benjamin Brown
+ * Copyright (C) 2004 Gregory Montoir
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ***************************************************************************/
+
+#ifndef __SFXPLAYER_H__
+#define __SFXPLAYER_H__
+
+#include "intern.h"
+
+struct SfxInstrument {
+    uint8_t *data;
+    uint16_t volume;
+};
+
+struct SfxModule {
+    const uint8_t *data;
+    uint16_t curPos;
+    uint8_t curOrder;
+    uint8_t numOrder;
+    uint8_t orderTable[0x80];
+    struct SfxInstrument samples[15];
+};
+
+struct SfxPattern {
+    uint16_t note_1;
+    uint16_t note_2;
+    uint16_t sampleStart;
+    uint8_t *sampleBuffer;
+    uint16_t sampleLen;
+    uint16_t loopPos;
+    uint8_t *loopData;
+    uint16_t loopLen;
+    uint16_t sampleVolume;
+};
+
+struct Mixer;
+struct Resource;
+struct Serializer;
+struct System;
+
+struct SfxPlayer {
+    struct Mixer *mixer;
+    struct Resource *res;
+    struct System *sys;
+
+    void *_mutex;
+    void *_timerId;
+    uint16_t _delay;
+    uint16_t _resNum;
+    struct SfxModule _sfxMod;
+    int16_t *_markVar;
+};
+
+void player_create(struct SfxPlayer*, struct Mixer *mix, struct Resource *res, struct System *stub);
+void player_init(struct SfxPlayer*);
+void player_free(struct SfxPlayer*);
+
+void player_setEventsDelay(struct SfxPlayer*, uint16_t delay);
+void player_loadSfxModule(struct SfxPlayer*, uint16_t resNum, uint16_t delay, uint8_t pos);
+void player_prepareInstruments(struct SfxPlayer*, const uint8_t *p);
+void player_start(struct SfxPlayer*);
+void player_stop(struct SfxPlayer*);
+void player_handleEvents(struct SfxPlayer*);
+void player_handlePattern(struct SfxPlayer*, uint8_t channel, const uint8_t *patternData);
+
+uint32_t player_eventsCallback(uint32_t interval, void *param);
+
+void player_saveOrLoad(struct SfxPlayer*, struct Serializer *ser);
+
+#endif
diff --git a/apps/plugins/xworld/sys.c b/apps/plugins/xworld/sys.c
new file mode 100644
index 0000000..4bcdfaf
--- /dev/null
+++ b/apps/plugins/xworld/sys.c
@@ -0,0 +1,942 @@
+/***************************************************************************
+ *             __________               __   ___.
+ *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
+ *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
+ *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
+ *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
+ *                     \/            \/     \/    \/            \/
+ * $Id$
+ *
+ * Copyright (C) 2014 Franklin Wei, Benjamin Brown
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ***************************************************************************/
+
+/* TODO: */
+/* vertical stride support (as of Dec. 2014, only the M:Robe 500 has a color,
+   vertical stride LCD) */
+
+/* monochrome/grayscale support (many of these targets have vertical strides,
+   so get that working first!) */
+
+#include "plugin.h"
+#include "lib/display_text.h"
+#include "lib/helper.h"
+#include "lib/playback_control.h"
+#include "lib/pluginlib_actions.h"
+#include "lib/pluginlib_bmp.h"
+#include "lib/pluginlib_exit.h"
+#include "sys.h"
+#include "parts.h"
+#include "engine.h"
+#include "keymaps.h"
+
+static struct System* save_sys;
+
+static bool sys_save_settings(struct System* sys)
+{
+    File f;
+    file_create(&f, false);
+    if(!file_open(&f, SETTINGS_FILE, sys->e->_saveDir, "wb"))
+    {
+        return false;
+    }
+    file_write(&f, &sys->settings, sizeof(sys->settings));
+    file_close(&f);
+    return true;
+}
+
+static void sys_rotate_keymap(struct System* sys)
+{
+    switch(sys->settings.rotation_option)
+    {
+    case 0:
+        sys->keymap.up = BTN_UP;
+        sys->keymap.down = BTN_DOWN;
+        sys->keymap.left = BTN_LEFT;
+        sys->keymap.right = BTN_RIGHT;
+#if (CONFIG_KEYPAD == SANSA_FUZEPLUS_PAD)
+        sys->keymap.upleft = BTN_UP_LEFT;
+        sys->keymap.upright = BTN_UP_RIGHT;
+        sys->keymap.downleft = BTN_DOWN_RIGHT;
+        sys->keymap.downright = BTN_DOWN_LEFT;
+#endif
+        break;
+    case 1:
+        sys->keymap.up = BTN_RIGHT;
+        sys->keymap.down = BTN_LEFT;
+        sys->keymap.left = BTN_UP;
+        sys->keymap.right = BTN_DOWN;
+#if (CONFIG_KEYPAD == SANSA_FUZEPLUS_PAD)
+        sys->keymap.upleft = BTN_UP_RIGHT;
+        sys->keymap.upright = BTN_DOWN_RIGHT;
+        sys->keymap.downleft = BTN_UP_LEFT;
+        sys->keymap.downright = BTN_DOWN_LEFT;
+#endif
+        break;
+    case 2:
+        sys->keymap.up = BTN_LEFT;
+        sys->keymap.down = BTN_RIGHT;
+        sys->keymap.left = BTN_DOWN;
+        sys->keymap.right = BTN_UP;
+#if (CONFIG_KEYPAD == SANSA_FUZEPLUS_PAD)
+        sys->keymap.upleft = BTN_DOWN_LEFT;
+        sys->keymap.upright = BTN_UP_LEFT;
+        sys->keymap.downleft = BTN_DOWN_RIGHT;
+        sys->keymap.downright = BTN_DOWN_LEFT;
+#endif
+        break;
+    default:
+        error("sys_rotate_keymap: fall-through!");
+    }
+}
+
+static bool sys_load_settings(struct System* sys)
+{
+    File f;
+    file_create(&f, false);
+    if(!file_open(&f, SETTINGS_FILE, sys->e->_saveDir, "rb"))
+    {
+        return false;
+    }
+    file_read(&f, &sys->settings, sizeof(sys->settings));
+    file_close(&f);
+    sys_rotate_keymap(sys);
+    return true;
+}
+
+void exit_handler(void)
+{
+    sys_save_settings(save_sys);
+    sys_stopAudio(save_sys);
+#ifdef HAVE_ADJUSTABLE_CPU_FREQ
+    rb->cpu_boost(false);
+#endif
+    backlight_use_settings();
+}
+
+static bool sys_do_help(void)
+{
+#ifdef HAVE_LCD_COLOR
+    rb->lcd_set_foreground(LCD_WHITE);
+    rb->lcd_set_background(LCD_BLACK);
+#endif
+    rb->lcd_setfont(FONT_UI);
+    char* help_text[] = {"XWorld", "",
+                         "XWorld", "is", "an", "interpreter", "for", "Another", "World,", "a", "fantastic", "game", "by", "Eric", "Chahi."
+                        };
+    struct style_text style[] = {
+        {0, TEXT_CENTER | TEXT_UNDERLINE},
+        LAST_STYLE_ITEM
+    };
+    return display_text(ARRAYLEN(help_text), help_text, style, NULL, true);
+}
+
+static const struct opt_items scaling_settings[3] = {
+    { "Disabled", -1 },
+    { "Fast"    , -1 },
+#ifdef HAVE_LCD_COLOR
+    { "Good"    , -1 }
+#endif
+};
+
+static const struct opt_items rotation_settings[3] = {
+    { "Disabled"     , -1 },
+    { "Clockwise"    , -1 },
+    { "Anticlockwise", -1 }
+};
+
+static void do_video_settings(struct System* sys)
+{
+    MENUITEM_STRINGLIST(menu, "Video Settings", NULL,
+                        "Negative",
+                        "Scaling",
+                        "Rotation",
+                        "Show FPS",
+                        "Zoom on code",
+                        "Back");
+    int sel = 0;
+    while(1)
+    {
+        switch(rb->do_menu(&menu, &sel, NULL, false))
+        {
+        case 0:
+            rb->set_bool("Negative", &sys->settings.negative_enabled);
+            break;
+        case 1:
+            rb->set_option("Scaling", &sys->settings.scaling_quality, INT, scaling_settings,
+#ifdef HAVE_LCD_COLOR
+                           3
+#else
+                           2
+#endif
+                           , NULL);
+            if(sys->settings.scaling_quality &&
+               sys->settings.zoom)
+            {
+                rb->splash(HZ*2, "Zoom automatically disabled.");
+                sys->settings.zoom = false;
+            }
+            break;
+        case 2:
+            rb->set_option("Rotation", &sys->settings.rotation_option, INT, rotation_settings, 3, NULL);
+            if(sys->settings.rotation_option &&
+               sys->settings.zoom)
+            {
+                rb->splash(HZ*2, "Zoom automatically disabled.");
+                sys->settings.zoom = false;
+            }
+            sys_rotate_keymap(sys);
+            break;
+        case 3:
+            rb->set_bool("Show FPS", &sys->settings.showfps);
+            break;
+        case 4:
+            rb->set_bool("Zoom on code", &sys->settings.zoom);
+            /* zoom only works with scaling and rotation disabled */
+            if(sys->settings.zoom &&
+               ( sys->settings.scaling_quality |
+                 sys->settings.rotation_option))
+            {
+                rb->splash(HZ*2, "Scaling and rotation automatically disabled.");
+                sys->settings.scaling_quality = 0;
+                sys->settings.rotation_option = 0;
+            }
+            break;
+        case 5:
+            rb->lcd_clear_display();
+            sys_save_settings(sys);
+            return;
+        }
+    }
+}
+
+#define MAX_SOUNDBUF_SIZE 512
+const struct opt_items sound_bufsize_options[] = {
+    {"8 samples"  , 8},
+    {"16 samples" , 16},
+    {"32 samples" , 32},
+    {"64 samples" , 64},
+    {"128 samples", 128},
+    {"256 samples", 256},
+    {"512 samples", 512},
+};
+
+static void do_sound_settings(struct System* sys)
+{
+    MENUITEM_STRINGLIST(menu, "Sound Settings", NULL,
+                        "Enabled",
+                        "Buffer Level",
+                        "Back",
+                       );
+    int sel = 0;
+    while(1)
+    {
+        switch(rb->do_menu(&menu, &sel, NULL, false))
+        {
+        case 0:
+            rb->set_bool("Enabled", &sys->settings.sound_enabled);
+            break;
+        case 1:
+            rb->set_option("Buffer Level", &sys->settings.sound_bufsize, INT,
+                           sound_bufsize_options, ARRAYLEN(sound_bufsize_options), NULL);
+            break;
+        case 2:
+            sys_save_settings(sys);
+            return;
+        }
+    }
+}
+
+static void sys_reset_settings(struct System* sys)
+{
+    sys->settings.negative_enabled = false;
+    sys->settings.rotation_option = 0;
+    sys->settings.scaling_quality = 1;
+    sys->settings.sound_enabled = true;
+    sys->settings.sound_bufsize = 64;
+    sys->settings.showfps = true;
+    sys->settings.zoom = false;
+    sys_rotate_keymap(sys);
+}
+
+static struct System* mainmenu_sysptr;
+
+static int mainmenu_cb(int action, const struct menu_item_ex *this_item)
+{
+    int idx = ((intptr_t)this_item);
+    if(action == ACTION_REQUEST_MENUITEM && !mainmenu_sysptr->loaded && (idx == 0 || idx == 8 || idx == 10))
+        return ACTION_EXIT_MENUITEM;
+    return action;
+}
+
+static AudioCallback audio_callback;
+static void* audio_param;
+static struct System* audio_sys;
+
+/************************************** MAIN MENU ***************************************/
+
+void sys_menu(struct System* sys)
+{
+    sys_stopAudio(sys);
+    rb->splash(0, "Loading...");
+    sys->loaded = engine_loadGameState(sys->e, 0);
+    rb->lcd_update();
+    mainmenu_sysptr = sys;
+    int sel = 0;
+    MENUITEM_STRINGLIST(menu, "XWorld Menu", mainmenu_cb,
+                        "Resume Game",          /* 0  */
+                        "Start New Game",       /* 1  */
+                        "Playback Control",     /* 2  */
+                        "Video Settings",       /* 3  */
+                        "Sound Settings",       /* 4  */
+                        "Fast Mode",            /* 5  */
+                        "Help",                 /* 6  */
+                        "Reset Settings",       /* 7  */
+                        "Load",                 /* 8  */
+                        "Save",                 /* 9  */
+                        "Quit without Saving",  /* 10 */
+                        "Save and Quit");       /* 11 */
+    bool quit = false;
+    while(!quit)
+    {
+        int item;
+        switch(item = rb->do_menu(&menu, &sel, NULL, false))
+        {
+        case 0:
+            quit = true;
+            break;
+        case 1:
+            vm_initForPart(&sys->e->vm, GAME_PART_FIRST); // This game part is the protection screen
+            quit = true;
+            break;
+        case 2:
+            playback_control(NULL);
+            break;
+        case 3:
+            do_video_settings(sys);
+            break;
+        case 4:
+            do_sound_settings(sys);
+            break;
+        case 5:
+            rb->set_bool("Fast Mode", &sys->e->vm._fastMode);
+            sys_save_settings(sys);
+            break;
+        case 6:
+            sys_do_help();
+            break;
+        case 7:
+            sys_reset_settings(sys);
+            sys_save_settings(sys);
+            break;
+        case 8:
+            rb->splash(0, "Loading...");
+            sys->loaded = engine_loadGameState(sys->e, 0);
+            rb->lcd_update();
+            break;
+        case 9:
+            sys->e->_stateSlot = 0;
+            rb->splash(0, "Saving...");
+            engine_saveGameState(sys->e, sys->e->_stateSlot, "quicksave");
+            rb->lcd_update();
+            break;
+        case 10:
+            engine_deleteGameState(sys->e, 0);
+            exit(PLUGIN_OK);
+            break;
+        case 11:
+            /* saves are NOT deleted on loading */
+            exit(PLUGIN_OK);
+            break;
+        default:
+            error("sys_menu: fall-through!");
+        }
+    }
+    rb->lcd_clear_display();
+    sys_startAudio(sys, audio_callback, audio_param);
+}
+
+void sys_init(struct System* sys, const char* title)
+{
+    (void) title;
+#ifdef HAVE_ADJUSTABLE_CPU_FREQ
+    rb->cpu_boost(true);
+#endif
+    backlight_ignore_timeout();
+    rb_atexit(exit_handler);
+    save_sys = sys;
+    rb->memset(&sys->input, 0, sizeof(sys->input));
+    sys->mutex_bitfield = ~0;
+    if(!sys_load_settings(sys))
+    {
+        sys_reset_settings(sys);
+    }
+}
+
+void sys_destroy(struct System* sys)
+{
+    (void) sys;
+    rb->splash(HZ, "Exiting...");
+}
+
+void sys_setPalette(struct System* sys, uint8_t start, uint8_t n, const uint8_t *buf)
+{
+    for(int i = start; i < start + n; ++i)
+    {
+        uint8_t c[3];
+        for (int j = 0; j < 3; j++) {
+            uint8_t col = buf[i * 3 + j];
+            c[j] = (col << 2) | (col & 3);
+        }
+#if (LCD_DEPTH > 16) && (LCD_DEPTH <= 24)
+        sys->palette[i] = (fb_data) {
+            c[2], c[1], c[0]
+        };
+#elif defined(HAVE_LCD_COLOR)
+        sys->palette[i] = FB_RGBPACK(c[0], c[1], c[2]);
+#elif LCD_DEPTH > 1
+        sys->palette[i] = LCD_BRIGHTNESS((c[0] + c[1] + c[2]) / 3);
+#endif
+    }
+}
+
+/****************************** THE MAIN DRAWING METHOD ********************************/
+
+void sys_copyRect(struct System* sys, uint16_t x, uint16_t y, uint16_t w, uint16_t h, const uint8_t *buf, uint32_t pitch)
+{
+    static int last_timestamp = 1;
+
+    /* get the address of the temporary framebuffer that has been allocated in the audiobuf */
+    fb_data* framebuffer = (fb_data*) sys->e->res._memPtrStart + MEM_BLOCK_SIZE + (4 * VID_PAGE_SIZE);
+    buf += y * pitch + x;
+
+    /************************ BLIT THE TEMPORARY FRAMEBUFFER ***********************/
+
+    if(sys->settings.rotation_option)
+    {
+        /* clockwise */
+        if(sys->settings.rotation_option == 1)
+        {
+            while (h--) {
+                /* one byte gives two pixels */
+                for (int i = 0; i < w / 2; ++i) {
+                    uint8_t pix1 = *(buf + i) >> 4;
+                    uint8_t pix2 = *(buf + i) & 0xF;
+#if defined(LCD_STRIDEFORMAT) && (LCD_STRIDEFORMAT == VERTICAL_STRIDE)
+                    framebuffer[( (h * 2)    ) * 320 + i] = sys->palette[pix1];
+                    framebuffer[( (h * 2) + 1) * 320 + i] = sys->palette[pix2];
+#else
+                    framebuffer[( (i * 2)    ) * 200 + h] = sys->palette[pix1];
+                    framebuffer[( (i * 2) + 1) * 200 + h] = sys->palette[pix2];
+#endif
+                }
+                buf += pitch;
+            }
+        }
+        /* counterclockwise */
+        else
+        {
+            while (h--) {
+                /* one byte gives two pixels */
+                for (int i = 0; i < w / 2; ++i) {
+                    uint8_t pix1 = *(buf + i) >> 4;
+                    uint8_t pix2 = *(buf + i) & 0xF;
+#if defined(LCD_STRIDEFORMAT) && (LCD_STRIDEFORMAT == VERTICAL_STRIDE)
+                    framebuffer[(200 - h * 2    ) * 320 + ( 320 - i )] = sys->palette[pix1];
+                    framebuffer[(200 - h * 2 - 1) * 320 + ( 320 - i )] = sys->palette[pix2];
+#else
+                    framebuffer[(320 - i * 2    ) * 200 + ( 200 - h )] = sys->palette[pix1];
+                    framebuffer[(320 - i * 2 - 1) * 200 + ( 200 - h )] = sys->palette[pix2];
+#endif
+                }
+                buf += pitch;
+            }
+        }
+    }
+    /* no rotation */
+    else
+    {
+        int next = 0;
+#if defined(LCD_STRIDEFORMAT) && (LCD_STRIDEFORMAT == VERTICAL_STRIDE)
+        for(int x = 0; x < w / 2; ++x)
+        {
+            for(int y = 0; y < h; ++y)
+            {
+                uint8_t pix1 = buf[ y * w + x ] >>  4;
+                uint8_t pix2 = buf[ y * w + x ] & 0xF;
+                framebuffer[(x + 0)*h + y] = sys->palette[pix1];
+                framebuffer[(x + 1)*h + y] = sys->palette[pix2];
+            }
+        }
+#else
+        while (h--) {
+            /* one byte gives two pixels */
+            for (int i = 0; i < w / 2; ++i) {
+                uint8_t pix1 = *(buf + i) >> 4;
+                uint8_t pix2 = *(buf + i) & 0xF;
+                framebuffer[next] = sys->palette[pix1];
+                ++next;
+                framebuffer[next] = sys->palette[pix2];
+                ++next;
+            }
+            buf += pitch;
+        }
+#endif
+    }
+
+    /*************************** NOW SCALE IT! ***************************/
+
+    if(sys->settings.scaling_quality)
+    {
+        struct bitmap in_bmp;
+        if(sys->settings.rotation_option)
+        {
+#if defined(LCD_STRIDEFORMAT) && (LCD_STRIDEFORMAT == VERTICAL_STRIDE)
+            in_bmp.width = 320;
+            in_bmp.height = 200;
+#else
+            in_bmp.width = 200;
+            in_bmp.height = 320;
+#endif
+        }
+        else
+        {
+#if defined(LCD_STRIDEFORMAT) && (LCD_STRIDEFORMAT == VERTICAL_STRIDE)
+            in_bmp.width = 200;
+            in_bmp.height = 320;
+#else
+            in_bmp.width = 320;
+            in_bmp.height = 200;
+#endif
+        }
+        in_bmp.data = (unsigned char*) framebuffer;
+        struct bitmap out_bmp;
+        out_bmp.width = LCD_WIDTH;
+        out_bmp.height = LCD_HEIGHT;
+        out_bmp.data = (unsigned char*) rb->lcd_framebuffer;
+
+#ifdef HAVE_LCD_COLOR
+        if(sys->settings.scaling_quality == 1)
+#endif
+            simple_resize_bitmap(&in_bmp, &out_bmp);
+#ifdef HAVE_LCD_COLOR
+        else
+            smooth_resize_bitmap(&in_bmp, &out_bmp);
+#endif
+    }
+    else
+    {
+#if defined(LCD_STRIDEFORMAT) && (LCD_STRIDEFORMAT == VERTICAL_STRIDE)
+        for(int x = 0; x < 320; ++x)
+        {
+            for(int y = 0; y < 200; ++y)
+            {
+                rb->lcd_set_foreground(framebuffer[x * 200 + y]);
+                rb->lcd_drawpixel(x, y);
+            }
+        }
+#else
+        if(sys->settings.zoom)
+        {
+            rb->lcd_bitmap_part(framebuffer, CODE_X, CODE_Y, STRIDE(SCREEN_MAIN, 320, 200), 0, 0, 320 - CODE_X, 200 - CODE_Y);
+        }
+        else
+        {
+            if(sys->settings.rotation_option)
+                rb->lcd_bitmap(framebuffer, 0, 0, 200, 320);
+            else
+                rb->lcd_bitmap(framebuffer, 0, 0, 320, 200);
+        }
+#endif
+    }
+
+    /************************************* APPLY FILTERS ******************************************/
+
+    if(sys->settings.negative_enabled)
+    {
+        for(int y = 0; y < LCD_HEIGHT; ++y)
+        {
+            for(int x = 0; x < LCD_WIDTH; ++x)
+            {
+#ifdef HAVE_LCD_COLOR
+                int r, g, b;
+                fb_data pix = rb->lcd_framebuffer[y * LCD_WIDTH + x];
+                r = RGB_UNPACK_RED  (pix);
+                g = RGB_UNPACK_GREEN(pix);
+                b = RGB_UNPACK_BLUE (pix);
+                rb->lcd_framebuffer[y * LCD_WIDTH + x] = LCD_RGBPACK(0xff - r, 0xff - g, 0xff - b);
+#else
+                rb->lcd_framebuffer[y * LCD_WIDTH + x] = LCD_BRIGHTNESS(0xff - rb->lcd_framebuffer[y * LCD_WIDTH + x]);
+#endif
+            }
+        }
+    }
+
+    /*********************** SHOW FPS *************************/
+
+    int current_time = sys_getTimeStamp(sys);
+    if(sys->settings.showfps)
+    {
+        rb->lcd_set_foreground(LCD_BLACK);
+        rb->lcd_set_background(LCD_WHITE);
+        /* use 1000 and not HZ here because getTimeStamp is in milliseconds */
+        rb->lcd_putsf(0, 0, "FPS: %d", 1000 / ((current_time - last_timestamp == 0) ? 1 : current_time - last_timestamp));
+    }
+    rb->lcd_update();
+    last_timestamp = sys_getTimeStamp(sys);
+}
+
+static void do_pause_menu(struct System* sys)
+{
+    sys_stopAudio(sys);
+    int sel = 0;
+    MENUITEM_STRINGLIST(menu, "XWorld Menu", NULL,
+                        "Resume Game",         /* 0 */
+                        "Start New Game",      /* 1 */
+                        "Playback Control",    /* 2 */
+                        "Video Settings",      /* 3 */
+                        "Sound Settings",      /* 4 */
+                        "Fast Mode",           /* 5 */
+                        "Enter Code",          /* 6 */
+                        "Help",                /* 7 */
+                        "Reset Settings",      /* 8 */
+                        "Load",                /* 9 */
+                        "Save",                /* 10 */
+                        "Quit");               /* 11 */
+
+    bool quit = false;
+    while(!quit)
+    {
+        switch(rb->do_menu(&menu, &sel, NULL, false))
+        {
+        case 0:
+            quit = true;
+            break;
+        case 1:
+            vm_initForPart(&sys->e->vm, GAME_PART_FIRST);
+            quit = true;
+            break;
+        case 2:
+            playback_control(NULL);
+            break;
+        case 3:
+            do_video_settings(sys);
+            break;
+        case 4:
+            do_sound_settings(sys);
+            break;
+        case 5:
+            rb->set_bool("Fast Mode", &sys->e->vm._fastMode);
+            sys_save_settings(sys);
+            break;
+        case 6:
+            sys->input.code = true;
+            quit = true;
+            break;
+        case 7:
+            sys_do_help();
+            break;
+        case 8:
+            sys_reset_settings(sys);
+            sys_save_settings(sys);
+            break;
+        case 9:
+            rb->splash(0, "Loading...");
+            sys->loaded = engine_loadGameState(sys->e, 0);
+            rb->lcd_update();
+            quit = true;
+            break;
+        case 10:
+            sys->e->_stateSlot = 0;
+            rb->splash(0, "Saving...");
+            engine_saveGameState(sys->e, sys->e->_stateSlot, "quicksave");
+            rb->lcd_update();
+            break;
+        case 11:
+            exit(PLUGIN_OK);
+            break;
+        }
+    }
+    rb->lcd_clear_display();
+    sys_startAudio(sys, audio_callback, audio_param);
+}
+
+void sys_processEvents(struct System* sys)
+{
+    int btn = rb->button_get(false);
+    btn &= ~BUTTON_REDRAW;
+    debug(DBG_SYS, "button is 0x%08x", btn);
+
+    /* exit early if we can */
+    if(btn == BUTTON_NONE)
+    {
+        return;
+    }
+
+    /* Ignore some buttons that cause errant input */
+#if (CONFIG_KEYPAD == IPOD_4G_PAD) || \
+    (CONFIG_KEYPAD == IPOD_3G_PAD) || \
+    (CONFIG_KEYPAD == IPOD_1G2G_PAD)
+    if(btn & 0x80000000)
+        return;
+#endif
+#if (CONFIG_KEYPAD == SANSA_E200_PAD)
+    if(btn == (BUTTON_SCROLL_FWD || BUTTON_SCROLL_BACK))
+        return;
+#endif
+#if (CONFIG_KEYPAD == SANSA_FUZEPLUS_PAD)
+    if(btn == (BUTTON_SELECT))
+        return;
+#endif
+
+    /* handle special keys first */
+    switch(btn)
+    {
+    case BTN_PAUSE:
+        do_pause_menu(sys);
+        sys->input.dirMask = 0;
+        sys->input.button = false;
+        /* exit early to avoid unwanted button presses being detected */
+        return;
+    default:
+        exit_on_usb(btn);
+        break;
+    }
+
+    /* handle releases */
+    if(btn & BUTTON_REL)
+    {
+        debug(DBG_SYS, "button_rel");
+        btn &= ~BUTTON_REL;
+
+        if(btn & BTN_FIRE)
+            sys->input.button = false;
+        if(btn & sys->keymap.up)
+            sys->input.dirMask &= ~DIR_UP;
+        if(btn & sys->keymap.down)
+            sys->input.dirMask &= ~DIR_DOWN;
+        if(btn & sys->keymap.left)
+            sys->input.dirMask &= ~DIR_LEFT;
+        if(btn & sys->keymap.right)
+            sys->input.dirMask &= ~DIR_RIGHT;
+#ifdef BTN_DOWN_LEFT
+        if(btn & sys->keymap.downleft)
+            sys->input.dirMask &= ~(DIR_DOWN | DIR_LEFT);
+#endif
+#ifdef BTN_DOWN_RIGHT
+        if(btn & sys->keymap.downright)
+            sys->input.dirMask &= ~(DIR_DOWN | DIR_RIGHT);
+#endif
+#ifdef BTN_UP_LEFT
+        if(btn & sys->keymap.upleft)
+            sys->input.dirMask &= ~(DIR_UP | DIR_LEFT);
+#endif
+#ifdef BTN_UP_RIGHT
+        if(btn & sys->keymap.upright)
+            sys->input.dirMask &= ~(DIR_UP | DIR_RIGHT);
+#endif
+    }
+    /* then handle presses */
+    else
+    {
+        if(btn & BTN_FIRE)
+            sys->input.button = true;
+        if(btn & sys->keymap.up)
+            sys->input.dirMask |= DIR_UP;
+        if(btn & sys->keymap.down)
+            sys->input.dirMask |= DIR_DOWN;
+        if(btn & sys->keymap.left)
+            sys->input.dirMask |= DIR_LEFT;
+        if(btn & sys->keymap.right)
+            sys->input.dirMask |= DIR_RIGHT;
+#ifdef BTN_DOWN_LEFT
+        if(btn & sys->keymap.downleft)
+            sys->input.dirMask |= (DIR_DOWN | DIR_LEFT);
+#endif
+#ifdef BTN_DOWN_RIGHT
+        if(btn & sys->keymap.downright)
+            sys->input.dirMask |= (DIR_DOWN | DIR_RIGHT);
+#endif
+#ifdef BTN_UP_LEFT
+        if(btn & sys->keymap.upleft)
+            sys->input.dirMask |= (DIR_UP | DIR_LEFT);
+#endif
+#ifdef BTN_UP_RIGHT
+        if(btn & sys->keymap.upright)
+            sys->input.dirMask |= (DIR_UP | DIR_RIGHT);
+#endif
+    }
+    debug(DBG_SYS, "dirMask is 0x%02x", sys->input.dirMask);
+    debug(DBG_SYS, "button is %s", sys->input.button == true ? "true" : "false");
+}
+
+void sys_sleep(struct System* sys, uint32_t duration)
+{
+    (void) sys;
+    /* duration is in ms */
+    rb->sleep(duration / 10);
+}
+
+uint32_t sys_getTimeStamp(struct System* sys)
+{
+    (void) sys;
+    return (uint32_t) (*rb->current_tick) * 10;
+}
+
+static int16_t rb_soundbuf [MAX_SOUNDBUF_SIZE] IBSS_ATTR;
+static int8_t temp_soundbuf[MAX_SOUNDBUF_SIZE] IBSS_ATTR;
+static void ICODE_ATTR get_more(const void** start, size_t* size)
+{
+    if(audio_sys->settings.sound_enabled)
+    {
+        audio_callback(audio_param, temp_soundbuf, audio_sys->settings.sound_bufsize);
+        /* convert xworld format (signed 8-bit) to rockbox format (signed 16-bit) */
+        for(int i = 0; i < audio_sys->settings.sound_bufsize; ++i)
+        {
+            rb_soundbuf[i] = temp_soundbuf[i] * 0x100;
+        }
+        *start = rb_soundbuf;
+        *size = audio_sys->settings.sound_bufsize;
+    }
+    else
+    {
+        *start = NULL;
+        *size = 0;
+    }
+}
+
+void sys_startAudio(struct System* sys, AudioCallback callback, void *param)
+{
+    (void) sys;
+    audio_callback = callback;
+    audio_param = param;
+    audio_sys = sys;
+    rb->pcm_play_data(get_more, NULL, NULL, 0);
+}
+
+void sys_stopAudio(struct System* sys)
+{
+    (void) sys;
+    rb->pcm_play_stop();
+}
+
+uint32_t sys_getOutputSampleRate(struct System* sys)
+{
+    (void) sys;
+    return rb->mixer_get_frequency();
+}
+
+/* pretty non-reentrant here, but who cares? it's not like someone
+   would want to run two instances of the game on Rockbox! :D */
+
+static uint32_t cur_delay;
+static void* cur_param;
+static TimerCallback user_callback;
+static void timer_callback(void)
+{
+    debug(DBG_SYS, "timer callback with delay of %d ms callback 0x%08x param 0x%08x", cur_delay, timer_callback, cur_param);
+    uint32_t ret = user_callback(cur_delay, cur_param);
+    if(ret != cur_delay)
+    {
+        cur_delay = ret;
+        rb->timer_register(1, NULL, TIMER_FREQ / 1000 * ret, timer_callback IF_COP(, CPU));
+    }
+}
+
+void *sys_addTimer(struct System* sys, uint32_t delay, TimerCallback callback, void *param)
+{
+    (void) sys;
+    debug(DBG_SYS, "registering timer with delay of %d ms callback 0x%08x param 0x%08x", delay, callback, param);
+    user_callback = callback;
+    cur_delay = delay;
+    cur_param = param;
+    rb->timer_register(1, NULL, TIMER_FREQ / 1000 * delay, timer_callback IF_COP(, CPU));
+    return NULL;
+}
+
+void sys_removeTimer(struct System* sys, void *timerId)
+{
+    (void) sys;
+    (void) timerId;
+    /* there's only one timer per game instance, so ignore both parameters */
+    rb->timer_unregister();
+}
+
+void *sys_createMutex(struct System* sys)
+{
+    if(!sys)
+        error("sys is NULL!");
+    for(int i = 0; i < MAX_MUTEXES; ++i)
+    {
+        if(sys->mutex_bitfield & (1 << i))
+        {
+            rb->mutex_init(&sys->mutex_memory[i]);
+            sys->mutex_bitfield |= (1 << i);
+            return &sys->mutex_memory[i];
+        }
+    }
+    warning("Out of mutexes!");
+    return NULL;
+}
+
+void sys_destroyMutex(struct System* sys, void *mutex)
+{
+    int mutex_number = ((char*)mutex - (char*)sys->mutex_memory) / sizeof(struct mutex); /* pointer arithmetic! check for bugs! */
+    sys->mutex_bitfield &= ~(1 << mutex_number);
+}
+
+void sys_lockMutex(struct System* sys, void *mutex)
+{
+    (void) sys;
+    debug(DBG_SYS, "calling mutex_lock");
+    rb->mutex_lock((struct mutex*) mutex);
+}
+
+void sys_unlockMutex(struct System* sys, void *mutex)
+{
+    (void) sys;
+    debug(DBG_SYS, "calling mutex_unlock");
+    rb->mutex_unlock((struct mutex*) mutex);
+}
+
+uint8_t* getOffScreenFramebuffer(struct System* sys)
+{
+    (void) sys;
+    return NULL;
+}
+
+void *sys_get_buffer(struct System* sys, size_t sz)
+{
+    if((signed int)sys->bytes_left - (signed int)sz >= 0)
+    {
+        void* ret = sys->membuf;
+        rb->memset(ret, 0, sz);
+        sys->membuf += sz;
+        return ret;
+    }
+    else
+    {
+        error("sys_get_buffer: out of memory!");
+        return NULL;
+    }
+}
+
+void MutexStack(struct MutexStack_t* s, struct System *stub, void *mutex)
+{
+    s->sys = stub;
+    s->_mutex = mutex;
+    sys_lockMutex(s->sys, s->_mutex);
+}
+
+void MutexStack_destroy(struct MutexStack_t* s)
+{
+    sys_unlockMutex(s->sys, s->_mutex);
+}
diff --git a/apps/plugins/xworld/sys.h b/apps/plugins/xworld/sys.h
new file mode 100644
index 0000000..f1920ac
--- /dev/null
+++ b/apps/plugins/xworld/sys.h
@@ -0,0 +1,146 @@
+/***************************************************************************
+ *             __________               __   ___.
+ *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
+ *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
+ *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
+ *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
+ *                     \/            \/     \/    \/            \/
+ * $Id$
+ *
+ * Copyright (C) 2014 Franklin Wei, Benjamin Brown
+ * Copyright (C) 2004 Gregory Montoir
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ***************************************************************************/
+
+#ifndef __XWORLD_SYS_H__
+#define __XWORLD_SYS_H__
+
+#include "intern.h"
+
+#define SYS_NEGATIVE_COLOR
+#define NUM_COLORS 16
+#define MAX_MUTEXES 16
+#define SETTINGS_FILE "settings.xfg"
+#define CODE_X 80
+#define CODE_Y 36
+
+enum {
+    DIR_LEFT  = 1 << 0,
+    DIR_RIGHT = 1 << 1,
+    DIR_UP    = 1 << 2,
+    DIR_DOWN  = 1 << 3
+};
+
+struct PlayerInput {
+
+    uint8_t dirMask;
+    bool button;
+    bool code;
+    bool pause;
+    bool quit;
+    char lastChar;
+    bool save, load;
+    bool fastMode;
+    int8_t stateSlot;
+};
+
+
+struct keymapping_t {
+    int up;
+    int down;
+    int left;
+    int right;
+#if (CONFIG_KEYPAD == SANSA_FUZEPLUS_PAD)
+    int upleft;
+    int upright;
+    int downleft;
+    int downright;
+#endif
+};
+
+typedef void (*AudioCallback)(void *param, uint8_t *stream, int len);
+typedef uint32_t (*TimerCallback)(uint32_t delay, void *param);
+
+struct System {
+    struct mutex mutex_memory[MAX_MUTEXES];
+    uint16_t mutex_bitfield;
+    struct PlayerInput input;
+    fb_data palette[NUM_COLORS];
+    struct Engine* e;
+    struct keymapping_t keymap;
+
+    void *membuf;
+    size_t bytes_left;
+
+    bool loaded;
+
+    struct {
+        bool negative_enabled;
+        /*
+          scaling quality:
+          0: off
+          1: fast
+          2: good (color only)
+        */
+        int scaling_quality;
+        /*
+          rotation:
+          0: off
+          1: cw
+          2: ccw
+        */
+        int rotation_option;
+
+        bool showfps;
+        bool sound_enabled;
+        int sound_bufsize;
+        bool zoom;
+    } settings;
+};
+
+void sys_init(struct System*, const char *title);
+void sys_menu(struct System*);
+void sys_destroy(struct System*);
+
+void sys_setPalette(struct System*, uint8_t s, uint8_t n, const uint8_t *buf);
+void sys_copyRect(struct System*, uint16_t x, uint16_t y, uint16_t w, uint16_t h, const uint8_t *buf, uint32_t pitch);
+
+void sys_processEvents(struct System*);
+void sys_sleep(struct System*, uint32_t duration);
+uint32_t sys_getTimeStamp(struct System*);
+
+void sys_startAudio(struct System*, AudioCallback callback, void *param);
+void sys_stopAudio(struct System*);
+uint32_t sys_getOutputSampleRate(struct System*);
+
+void *sys_addTimer(struct System*, uint32_t delay, TimerCallback callback, void *param);
+void sys_removeTimer(struct System*, void *timerId);
+
+void *sys_createMutex(struct System*);
+void sys_destroyMutex(struct System*, void *mutex);
+void sys_lockMutex(struct System*, void *mutex);
+void sys_unlockMutex(struct System*, void *mutex);
+
+/* a quick 'n dirty function to get some bytes in the audio buffer after zeroing them */
+/* pretty much does the same thing as calloc, though much uglier and there's no free() */
+void *sys_get_buffer(struct System*, size_t);
+
+uint8_t* getOffScreenFramebuffer(struct System*);
+
+struct MutexStack_t {
+    struct System *sys;
+    void *_mutex;
+};
+
+void MutexStack(struct MutexStack_t*, struct System*, void*);
+void MutexStack_destroy(struct MutexStack_t*);
+
+#endif
diff --git a/apps/plugins/xworld/util.c b/apps/plugins/xworld/util.c
new file mode 100644
index 0000000..b82691a
--- /dev/null
+++ b/apps/plugins/xworld/util.c
@@ -0,0 +1,82 @@
+/***************************************************************************
+ *             __________               __   ___.
+ *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
+ *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
+ *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
+ *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
+ *                     \/            \/     \/    \/            \/
+ * $Id$
+ *
+ * Copyright (C) 2014 Franklin Wei, Benjamin Brown
+ * Copyright (C) 2004 Gregory Montoir
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ***************************************************************************/
+
+#include "plugin.h"
+#include "lib/pluginlib_exit.h"
+#include <stdarg.h>
+#include "util.h"
+uint16_t g_debugMask;
+
+#ifdef XWORLD_DEBUG
+void debug_real(uint16_t cm, const char *msg, ...) {
+#ifdef ROCKBOX_HAS_LOGF
+    char buf[1024];
+    if (cm & g_debugMask) {
+        va_list va;
+        va_start(va, msg);
+        rb->vsnprintf(buf, 1024, msg, va);
+        va_end(va);
+        LOGF("%s", buf);
+    }
+#else
+    (void) cm;
+    (void) msg;
+#endif
+}
+#endif
+
+void error(const char *msg, ...) {
+    char buf[1024];
+    va_list va;
+    va_start(va, msg);
+    rb->vsnprintf(buf, 1024, msg, va);
+    va_end(va);
+    rb->splashf(HZ * 2, "ERROR: %s!", buf);
+    LOGF("ERROR: %s", buf);
+    exit(-1);
+}
+
+void warning(const char *msg, ...) {
+    char buf[1024];
+    va_list va;
+    va_start(va, msg);
+    rb->vsnprintf(buf, 1024, msg, va);
+    va_end(va);
+    rb->splashf(HZ * 2, "WARNING: %s!", buf);
+    LOGF("WARNING: %s", buf);
+}
+
+void string_lower(char *p) {
+    for (; *p; ++p) {
+        if (*p >= 'A' && *p <= 'Z') {
+            *p += 'a' - 'A';
+        }
+    }
+}
+
+void string_upper(char *p) {
+    for (; *p; ++p) {
+        if (*p >= 'a' && *p <= 'z') {
+            *p += 'A' - 'a';
+        }
+    }
+}
diff --git a/apps/plugins/xworld/util.h b/apps/plugins/xworld/util.h
new file mode 100644
index 0000000..8852335
--- /dev/null
+++ b/apps/plugins/xworld/util.h
@@ -0,0 +1,59 @@
+/***************************************************************************
+ *             __________               __   ___.
+ *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
+ *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
+ *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
+ *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
+ *                     \/            \/     \/    \/            \/
+ * $Id$
+ *
+ * Copyright (C) 2014 Franklin Wei, Benjamin Brown
+ * Copyright (C) 2004 Gregory Montoir
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ***************************************************************************/
+
+#ifndef __UTIL_H__
+#define __UTIL_H__
+
+#include "intern.h"
+
+/* #define XWORLD_DEBUG */
+
+#ifdef XWORLD_DEBUG
+#define debug(m,f,...) debug_real(m, f, ##__VA_ARGS__)
+#else
+#define debug(m,f,...)
+#endif
+
+enum {
+    DBG_VM = 1 << 0,
+    DBG_BANK  = 1 << 1,
+    DBG_VIDEO = 1 << 2,
+    DBG_SND   = 1 << 3,
+    DBG_SER   = 1 << 4,
+    DBG_INFO  = 1 << 5,
+    DBG_RES   = 1 << 6,
+    DBG_FILE  = 1 << 7,
+    DBG_SYS   = 1 << 8,
+    DBG_ENG   = 1 << 9
+};
+
+extern uint16_t g_debugMask;
+#ifdef XWORLD_DEBUG
+extern void debug_real(uint16_t cm, const char *msg, ...);
+#endif
+extern void error(const char *msg, ...);
+extern void warning(const char *msg, ...);
+
+extern void string_lower(char *p);
+extern void string_upper(char *p);
+
+#endif
diff --git a/apps/plugins/xworld/video.c b/apps/plugins/xworld/video.c
new file mode 100644
index 0000000..b4182df
--- /dev/null
+++ b/apps/plugins/xworld/video.c
@@ -0,0 +1,1141 @@
+/***************************************************************************
+ *             __________               __   ___.
+ *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
+ *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
+ *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
+ *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
+ *                     \/            \/     \/    \/            \/
+ * $Id$
+ *
+ * Copyright (C) 2014 Franklin Wei, Benjamin Brown
+ * Copyright (C) 2004 Gregory Montoir
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ***************************************************************************/
+
+#include "video.h"
+#include "video_data.h"
+#include "resource.h"
+#include "serializer.h"
+#include "sys.h"
+#include "file.h"
+
+void polygon_readVertices(struct Polygon* g, const uint8_t *p, uint16_t zoom) {
+    g->bbw = (*p++) * zoom / 64;
+    g->bbh = (*p++) * zoom / 64;
+    g->numPoints = *p++;
+    assert((g->numPoints & 1) == 0 && g->numPoints < MAX_POINTS);
+
+    //Read all points, directly from bytecode segment
+    for (int i = 0; i < g->numPoints; ++i) {
+        struct Point *pt = &g->points[i];
+        pt->x = (*p++) * zoom / 64;
+        pt->y = (*p++) * zoom / 64;
+    }
+}
+
+void video_create(struct Video* v, struct Resource* res, struct System* sys)
+{
+    v->res = res;
+    v->sys = sys;
+}
+
+void video_init(struct Video* v) {
+    v->paletteIdRequested = NO_PALETTE_CHANGE_REQUESTED;
+    v->page_data = v->res->_memPtrStart + MEM_BLOCK_SIZE;
+
+    rb->memset(v->page_data, 0, 4 * VID_PAGE_SIZE);
+
+    for (int i = 0; i < 4; ++i) {
+        v->_pagePtrs[i] = v->page_data + i * VID_PAGE_SIZE;
+    }
+
+    v->_curPagePtr3 = video_getPagePtr(v, 1);
+    v->_curPagePtr2 = video_getPagePtr(v, 2);
+
+    video_changePagePtr1(v, 0xFE);
+
+    v->_interpTable[0] = 0x4000;
+
+    for (int i = 1; i < 0x400; ++i) {
+        v->_interpTable[i] = 0x4000 / i;
+    }
+}
+
+void video_setDataBuffer(struct Video* v, uint8_t *dataBuf, uint16_t offset) {
+
+    v->_dataBuf = dataBuf;
+    v->_pData.pc = dataBuf + offset;
+}
+
+
+/*  A shape can be given in two different ways:
+
+    - A list of screenspace vertices.
+    - A list of objectspace vertices, based on a delta from the first vertex.
+
+    This is a recursive function. */
+void video_readAndDrawPolygon(struct Video* v, uint8_t color, uint16_t zoom, const struct Point *pt) {
+
+    uint8_t i = scriptPtr_fetchByte(&v->_pData);
+
+    //This is
+    if (i >= 0xC0) {    // 0xc0 = 192
+
+        // WTF ?
+        if (color & 0x80) {   //0x80 = 128 (1000 0000)
+            color = i & 0x3F; //0x3F =  63 (0011 1111)
+        }
+
+        // pc is misleading here since we are not reading bytecode but only
+        // vertices informations.
+        polygon_readVertices(&v->polygon, v->_pData.pc, zoom);
+
+        video_fillPolygon(v, color, zoom, pt);
+
+
+
+    } else {
+        i &= 0x3F;  //0x3F = 63
+        if (i == 1) {
+            warning("video_readAndDrawPolygon() ec=0x%X (i != 2)", 0xF80);
+        } else if (i == 2) {
+            video_readAndDrawPolygonHierarchy(v, zoom, pt);
+
+        } else {
+            warning("video_readAndDrawPolygon() ec=0x%X (i != 2)", 0xFBB);
+        }
+    }
+
+
+
+}
+
+void video_fillPolygon(struct Video* v, uint16_t color, uint16_t zoom, const struct Point *pt) {
+
+    (void) zoom;
+
+    if (v->polygon.bbw == 0 && v->polygon.bbh == 1 && v->polygon.numPoints == 4) {
+        video_drawPoint(v, color, pt->x, pt->y);
+
+        return;
+    }
+
+    int16_t x1 = pt->x - v->polygon.bbw / 2;
+    int16_t x2 = pt->x + v->polygon.bbw / 2;
+    int16_t y1 = pt->y - v->polygon.bbh / 2;
+    int16_t y2 = pt->y + v->polygon.bbh / 2;
+
+    if (x1 > 319 || x2 < 0 || y1 > 199 || y2 < 0)
+        return;
+
+    v->_hliney = y1;
+
+    uint16_t i, j;
+    i = 0;
+    j = v->polygon.numPoints - 1;
+
+    x2 = v->polygon.points[i].x + x1;
+    x1 = v->polygon.points[j].x + x1;
+
+    ++i;
+    --j;
+
+    drawLine drawFct;
+    if (color < 0x10) {
+        drawFct = &video_drawLineN;
+    } else if (color > 0x10) {
+        drawFct = &video_drawLineP;
+    } else {
+        drawFct = &video_drawLineBlend;
+    }
+
+    uint32_t cpt1 = x1 << 16;
+    uint32_t cpt2 = x2 << 16;
+
+    while (1) {
+        v->polygon.numPoints -= 2;
+        if (v->polygon.numPoints == 0) {
+#if TRACE_FRAMEBUFFER
+            video_dumpFrameBuffers(v, "fillPolygonEnd");
+#endif
+#if TRACE_BG_BUFFER
+            video_dumpBackGroundBuffer(v);
+#endif
+            break;
+        }
+        uint16_t h;
+        int32_t step1 = video_calcStep(v, &v->polygon.points[j + 1], &v->polygon.points[j], &h);
+        int32_t step2 = video_calcStep(v, &v->polygon.points[i - 1], &v->polygon.points[i], &h);
+
+        ++i;
+        --j;
+
+        cpt1 = (cpt1 & 0xFFFF0000) | 0x7FFF;
+        cpt2 = (cpt2 & 0xFFFF0000) | 0x8000;
+
+        if (h == 0) {
+            cpt1 += step1;
+            cpt2 += step2;
+        } else {
+            for (; h != 0; --h) {
+                if (v->_hliney >= 0) {
+                    x1 = cpt1 >> 16;
+                    x2 = cpt2 >> 16;
+                    if (x1 <= 319 && x2 >= 0) {
+                        if (x1 < 0) x1 = 0;
+                        if (x2 > 319) x2 = 319;
+                        (*drawFct)(v, x1, x2, color);
+                    }
+                }
+                cpt1 += step1;
+                cpt2 += step2;
+                ++v->_hliney;
+                if (v->_hliney > 199) return;
+            }
+        }
+
+#if TRACE_FRAMEBUFFER
+        video_dumpFrameBuffers(v, "fillPolygonChild");
+#endif
+#if TRACE_BG_BUFFER
+
+        video_dumpBackGroundBuffer(v);
+#endif
+    }
+}
+
+/*
+  What is read from the bytecode is not a pure screnspace polygon but a polygonspace polygon.
+
+*/
+void video_readAndDrawPolygonHierarchy(struct Video* v, uint16_t zoom, const struct Point *pgc) {
+
+    struct Point pt = *pgc;
+    pt.x -= scriptPtr_fetchByte(&v->_pData) * zoom / 64;
+    pt.y -= scriptPtr_fetchByte(&v->_pData) * zoom / 64;
+
+    int16_t childs = scriptPtr_fetchByte(&v->_pData);
+    debug(DBG_VIDEO, "video_readAndDrawPolygonHierarchy childs=%d", childs);
+
+    for ( ; childs >= 0; --childs) {
+
+        uint16_t off = scriptPtr_fetchWord(&v->_pData);
+
+        struct Point po;
+        po = pt;
+        po.x += scriptPtr_fetchByte(&v->_pData) * zoom / 64;
+        po.y += scriptPtr_fetchByte(&v->_pData) * zoom / 64;
+
+        uint16_t color = 0xFF;
+        uint16_t _bp = off;
+        off &= 0x7FFF;
+
+        if (_bp & 0x8000) {
+            color = *v->_pData.pc & 0x7F;
+            v->_pData.pc += 2;
+        }
+
+        uint8_t *bak = v->_pData.pc;
+        v->_pData.pc = v->_dataBuf + off * 2;
+
+
+        video_readAndDrawPolygon(v, color, zoom, &po);
+
+
+        v->_pData.pc = bak;
+    }
+
+
+}
+
+int32_t video_calcStep(struct Video* v, const struct Point *p1, const struct Point *p2, uint16_t *dy) {
+    (void) v;
+    *dy = p2->y - p1->y;
+    return (p2->x - p1->x) * v->_interpTable[*dy] * 4;
+}
+
+void video_drawString(struct Video* v, uint8_t color, uint16_t x, uint16_t y, uint16_t stringId) {
+
+    const struct StrEntry *se = video_stringsTableEng;
+
+    //Search for the location where the string is located.
+    while (se->id != END_OF_STRING_DICTIONARY && se->id != stringId)
+        ++se;
+
+    debug(DBG_VIDEO, "video_drawString(%d, %d, %d, '%s')", color, x, y, se->str);
+
+    //Not found
+    if (se->id == END_OF_STRING_DICTIONARY)
+        return;
+
+
+    //Used if the string contains a return carriage.
+    uint16_t xOrigin = x;
+    int len = rb->strlen(se->str);
+    for (int i = 0; i < len; ++i) {
+
+        if (se->str[i] == '\n') {
+            y += 8;
+            x = xOrigin;
+            continue;
+        }
+
+        video_drawChar(v, se->str[i], x, y, color, v->_curPagePtr1);
+        x++;
+
+    }
+}
+
+void video_drawChar(struct Video* v, uint8_t character, uint16_t x, uint16_t y, uint8_t color, uint8_t *buf) {
+    (void) v;
+    if (x <= 39 && y <= 192) {
+
+        /* each character is 8x8 */
+        const uint8_t *ft = video_font + (character - ' ') * 8;
+
+        /* x is multiplied by 4 and not 8 because there are two pixels per byte */
+        uint8_t *p = buf + x * 4 + y * 160;
+
+        for (int j = 0; j < 8; ++j) {
+            uint8_t ch = *(ft + j);
+            for (int i = 0; i < 4; ++i) {
+                uint8_t b = *(p + i);
+                uint8_t cmask = 0xFF;
+                uint8_t colb = 0;
+                if (ch & 0x80) {
+                    colb |= color << 4;
+                    cmask &= 0x0F;
+                }
+                ch <<= 1;
+                if (ch & 0x80) {
+                    colb |= color;
+                    cmask &= 0xF0;
+                }
+                ch <<= 1;
+                *(p + i) = (b & cmask) | colb;
+            }
+            /* skip to the next line (320 pixels = 160 bytes) */
+            p += 160;
+        }
+    }
+}
+
+void video_drawPoint(struct Video* v, uint8_t color, int16_t x, int16_t y) {
+    debug(DBG_VIDEO, "drawPoint(%d, %d, %d)", color, x, y);
+    if (x >= 0 && x <= 319 && y >= 0 && y <= 199) {
+        uint16_t off = y * 160 + x / 2;
+
+        uint8_t cmasko, cmaskn;
+        if (x & 1) {
+            cmaskn = 0x0F;
+            cmasko = 0xF0;
+        } else {
+            cmaskn = 0xF0;
+            cmasko = 0x0F;
+        }
+
+        uint8_t colb = (color << 4) | color;
+        if (color == 0x10) {
+            cmaskn &= 0x88;
+            cmasko = ~cmaskn;
+            colb = 0x88;
+        } else if (color == 0x11) {
+            colb = *(v->_pagePtrs[0] + off);
+        }
+        uint8_t b = *(v->_curPagePtr1 + off);
+        *(v->_curPagePtr1 + off) = (b & cmasko) | (colb & cmaskn);
+    }
+}
+
+/* Blend a line in the current framebuffer (v->_curPagePtr1)
+ */
+void video_drawLineBlend(struct Video* v, int16_t x1, int16_t x2, uint8_t color) {
+    /* silence warnings without XWORLD_DEBUG */
+    (void) color;
+    debug(DBG_VIDEO, "drawLineBlend(%d, %d, %d)", x1, x2, color);
+    int16_t xmax = MAX(x1, x2);
+    int16_t xmin = MIN(x1, x2);
+    uint8_t *p = v->_curPagePtr1 + v->_hliney * 160 + xmin / 2;
+
+    uint16_t w = xmax / 2 - xmin / 2 + 1;
+    uint8_t cmaske = 0;
+    uint8_t cmasks = 0;
+    if (xmin & 1) {
+        --w;
+        cmasks = 0xF7;
+    }
+    if (!(xmax & 1)) {
+        --w;
+        cmaske = 0x7F;
+    }
+
+    if (cmasks != 0) {
+        *p = (*p & cmasks) | 0x08;
+        ++p;
+    }
+    while (w--) {
+        *p = (*p & 0x77) | 0x88;
+        ++p;
+    }
+    if (cmaske != 0) {
+        *p = (*p & cmaske) | 0x80;
+        ++p;
+    }
+
+
+}
+
+void video_drawLineN(struct Video* v, int16_t x1, int16_t x2, uint8_t color) {
+    debug(DBG_VIDEO, "drawLineN(%d, %d, %d)", x1, x2, color);
+    int16_t xmax = MAX(x1, x2);
+    int16_t xmin = MIN(x1, x2);
+    uint8_t *p = v->_curPagePtr1 + v->_hliney * 160 + xmin / 2;
+
+    uint16_t w = xmax / 2 - xmin / 2 + 1;
+    uint8_t cmaske = 0;
+    uint8_t cmasks = 0;
+    if (xmin & 1) {
+        --w;
+        cmasks = 0xF0;
+    }
+    if (!(xmax & 1)) {
+        --w;
+        cmaske = 0x0F;
+    }
+
+    uint8_t colb = ((color & 0xF) << 4) | (color & 0xF);
+    if (cmasks != 0) {
+        *p = (*p & cmasks) | (colb & 0x0F);
+        ++p;
+    }
+    while (w--) {
+        *p++ = colb;
+    }
+    if (cmaske != 0) {
+        *p = (*p & cmaske) | (colb & 0xF0);
+        ++p;
+    }
+
+
+}
+
+void video_drawLineP(struct Video* v, int16_t x1, int16_t x2, uint8_t color) {
+    /* silence warnings without XWORLD_DEBUG */
+    (void) color;
+    debug(DBG_VIDEO, "drawLineP(%d, %d, %d)", x1, x2, color);
+    int16_t xmax = MAX(x1, x2);
+    int16_t xmin = MIN(x1, x2);
+    uint16_t off = v->_hliney * 160 + xmin / 2;
+    uint8_t *p = v->_curPagePtr1 + off;
+    uint8_t *q = v->_pagePtrs[0] + off;
+
+    uint8_t w = xmax / 2 - xmin / 2 + 1;
+    uint8_t cmaske = 0;
+    uint8_t cmasks = 0;
+    if (xmin & 1) {
+        --w;
+        cmasks = 0xF0;
+    }
+    if (!(xmax & 1)) {
+        --w;
+        cmaske = 0x0F;
+    }
+
+    if (cmasks != 0) {
+        *p = (*p & cmasks) | (*q & 0x0F);
+        ++p;
+        ++q;
+    }
+    while (w--) {
+        *p++ = *q++;
+    }
+    if (cmaske != 0) {
+        *p = (*p & cmaske) | (*q & 0xF0);
+        ++p;
+        ++q;
+    }
+
+}
+
+uint8_t *video_getPagePtr(struct Video* v, uint8_t page) {
+    uint8_t *p;
+    if (page <= 3) {
+        p = v->_pagePtrs[page];
+    } else {
+        switch (page) {
+        case 0xFF:
+            p = v->_curPagePtr3;
+            break;
+        case 0xFE:
+            p = v->_curPagePtr2;
+            break;
+        default:
+            p = v->_pagePtrs[0]; // XXX check
+            warning("video_getPagePtr() p != [0,1,2,3,0xFF,0xFE] == 0x%X", page);
+            break;
+        }
+    }
+    return p;
+}
+
+
+
+void video_changePagePtr1(struct Video* v, uint8_t page) {
+    debug(DBG_VIDEO, "video_changePagePtr1(%d)", page);
+    v->_curPagePtr1 = video_getPagePtr(v, page);
+}
+
+
+
+void video_fillPage(struct Video* v, uint8_t pageId, uint8_t color) {
+    debug(DBG_VIDEO, "video_fillPage(%d, %d)", pageId, color);
+    uint8_t *p = video_getPagePtr(v, pageId);
+
+    // Since a palette indice is coded on 4 bits, we need to duplicate the
+    // clearing color to the upper part of the byte.
+    uint8_t c = (color << 4) | color;
+
+    rb->memset(p, c, VID_PAGE_SIZE);
+
+#if TRACE_FRAMEBUFFER
+    video_dumpFrameBuffers(v, "-fillPage");
+#endif
+#if TRACE_BG_BUFFER
+
+    video_dumpBackGroundBuffer(v);
+#endif
+}
+
+
+
+
+
+#if TRACE_FRAMEBUFFER
+#define SCREENSHOT_BPP 3
+int traceFrameBufferCounter = 0;
+uint8_t allFrameBuffers[640 * 400 * SCREENSHOT_BPP];
+
+#endif
+
+
+
+
+
+
+/*  This opcode is used once the background of a scene has been drawn in one of the framebuffer:
+    it is copied in the current framebuffer at the start of a new frame in order to improve performances. */
+void video_copyPage(struct Video* v, uint8_t srcPageId, uint8_t dstPageId, int16_t vscroll) {
+
+    debug(DBG_VIDEO, "video_copyPage(%d, %d)", srcPageId, dstPageId);
+
+    if (srcPageId == dstPageId)
+        return;
+
+    uint8_t *p;
+    uint8_t *q;
+
+    if (srcPageId >= 0xFE || !((srcPageId &= 0xBF) & 0x80)) {
+        p = video_getPagePtr(v, srcPageId);
+        q = video_getPagePtr(v, dstPageId);
+        memcpy(q, p, VID_PAGE_SIZE);
+
+    } else {
+        p = video_getPagePtr(v, srcPageId & 3);
+        q = video_getPagePtr(v, dstPageId);
+        if (vscroll >= -199 && vscroll <= 199) {
+            uint16_t h = 200;
+            if (vscroll < 0) {
+                h += vscroll;
+                p += -vscroll * 160;
+            } else {
+                h -= vscroll;
+                q += vscroll * 160;
+            }
+            memcpy(q, p, h * 160);
+        }
+    }
+
+
+#if TRACE_FRAMEBUFFER
+    char name[256];
+    rb->memset(name, 0, sizeof(name));
+    sprintf(name, "copyPage_0x%X_to_0x%X", (p - v->_pagePtrs[0]) / VID_PAGE_SIZE, (q - v->_pagePtrs[0]) / VID_PAGE_SIZE);
+    dumpFrameBuffers(name);
+#endif
+}
+
+
+
+
+void video_copyPagePtr(struct Video* v, const uint8_t *src) {
+    debug(DBG_VIDEO, "video_copyPagePtr()");
+    uint8_t *dst = v->_pagePtrs[0];
+    int h = 200;
+    while (h--) {
+        int w = 40;
+        while (w--) {
+            uint8_t p[] = {
+                *(src + 8000 * 3),
+                *(src + 8000 * 2),
+                *(src + 8000 * 1),
+                *(src + 8000 * 0)
+            };
+            for(int j = 0; j < 4; ++j) {
+                uint8_t acc = 0;
+                for (int i = 0; i < 8; ++i) {
+                    acc <<= 1;
+                    acc |= (p[i & 3] & 0x80) ? 1 : 0;
+                    p[i & 3] <<= 1;
+                }
+                *dst++ = acc;
+            }
+            ++src;
+        }
+    }
+
+
+}
+
+/*
+  uint8_t *video_allocPage() {
+  uint8_t *buf = (uint8_t *)malloc(VID_PAGE_SIZE);
+  rb->memset(buf, 0, VID_PAGE_SIZE);
+  return buf;
+  }
+*/
+
+
+
+#if TRACE_FRAMEBUFFER
+int dumpPaletteCursor = 0;
+#endif
+
+/*
+  Note: The palettes set used to be allocated on the stack but I moved it to
+  the heap so I could dump the four framebuffer and follow how
+  frames are generated.
+*/
+uint8_t pal[NUM_COLORS * 3]; //3 = BYTES_PER_PIXEL
+void video_changePal(struct Video* v, uint8_t palNum) {
+    debug(DBG_VIDEO, "video_changePal(v=0x%08x, palNum=%d",  v, palNum);
+    if (palNum >= 32)
+        return;
+
+    uint8_t *p = v->res->segPalettes + palNum * 32; //colors are coded on 2bytes (565) for 16 colors = 32
+    debug(DBG_VIDEO, "segPalettes: 0x%08x", v->res->segPalettes);
+    // Moved to the heap, legacy code used to allocate the palette
+    // on the stack.
+    //uint8_t pal[NUM_COLORS * 3]; //3 = BYTES_PER_PIXEL
+
+    for (int i = 0; i < NUM_COLORS; ++i)
+    {
+        debug(DBG_VIDEO, "i: %d", i);
+        debug(DBG_VIDEO, "p: 0x%08x", p);
+        uint8_t c1 = *(p + 0);
+        uint8_t c2 = *(p + 1);
+        p += 2;
+        pal[i * 3 + 0] = ((c1 & 0x0F) << 2) | ((c1 & 0x0F) >> 2); // r
+        pal[i * 3 + 1] = ((c2 & 0xF0) >> 2) | ((c2 & 0xF0) >> 6); // g
+        pal[i * 3 + 2] = ((c2 & 0x0F) >> 2) | ((c2 & 0x0F) << 2); // b
+    }
+
+    sys_setPalette(v->sys, 0, NUM_COLORS, pal);
+    v->currentPaletteId = palNum;
+
+
+#if TRACE_PALETTE
+    printf("\nuint8_t dumpPalette[48] = {\n");
+    for (int i = 0; i < NUM_COLORS; ++i)
+    {
+        printf("0x%X,0x%X,0x%X,", pal[i * 3 + 0], pal[i * 3 + 1], pal[i * 3 + 2]);
+    }
+    printf("\n};\n");
+#endif
+
+
+#if TRACE_FRAMEBUFFER
+    dumpPaletteCursor++;
+#endif
+}
+
+void video_updateDisplay(struct Video* v, uint8_t pageId) {
+
+    debug(DBG_VIDEO, "video_updateDisplay(%d)", pageId);
+
+    if (pageId != 0xFE) {
+        if (pageId == 0xFF) {
+            /* swap ptrs 2 and 3 */
+            uint8_t* temp = v->_curPagePtr3;
+            v->_curPagePtr3 = v->_curPagePtr2;
+            v->_curPagePtr2 = temp;
+        } else {
+            v->_curPagePtr2 = video_getPagePtr(v, pageId);
+        }
+    }
+
+    //Check if we need to change the palette
+    if (v->paletteIdRequested != NO_PALETTE_CHANGE_REQUESTED) {
+        video_changePal(v, v->paletteIdRequested);
+        v->paletteIdRequested = NO_PALETTE_CHANGE_REQUESTED;
+    }
+
+    //Q: Why 160 ?
+    //A: Because one byte gives two palette indices so
+    //   we only need to move 320/2 per line.
+    sys_copyRect(v->sys, 0, 0, 320, 200, v->_curPagePtr2, 160);
+
+#if TRACE_FRAMEBUFFER
+    dumpFrameBuffer(v->_curPagePtr2, allFrameBuffers, 320, 200);
+#endif
+}
+
+void video_saveOrLoad(struct Video* v, struct Serializer *ser) {
+    uint8_t mask = 0;
+    if (ser->_mode == SM_SAVE) {
+        for (int i = 0; i < 4; ++i) {
+            if (v->_pagePtrs[i] == v->_curPagePtr1)
+                mask |= i << 4;
+            if (v->_pagePtrs[i] == v->_curPagePtr2)
+                mask |= i << 2;
+            if (v->_pagePtrs[i] == v->_curPagePtr3)
+                mask |= i << 0;
+        }
+    }
+    struct Entry entries[] = {
+        SE_INT(&v->currentPaletteId, SES_INT8, VER(1)),
+        SE_INT(&v->paletteIdRequested, SES_INT8, VER(1)),
+        SE_INT(&mask, SES_INT8, VER(1)),
+        SE_ARRAY(v->_pagePtrs[0], VID_PAGE_SIZE, SES_INT8, VER(1)),
+        SE_ARRAY(v->_pagePtrs[1], VID_PAGE_SIZE, SES_INT8, VER(1)),
+        SE_ARRAY(v->_pagePtrs[2], VID_PAGE_SIZE, SES_INT8, VER(1)),
+        SE_ARRAY(v->_pagePtrs[3], VID_PAGE_SIZE, SES_INT8, VER(1)),
+        SE_END()
+    };
+    ser_saveOrLoadEntries(ser, entries);
+
+    if (ser->_mode == SM_LOAD) {
+        v->_curPagePtr1 = v->_pagePtrs[(mask >> 4) & 0x3];
+        v->_curPagePtr2 = v->_pagePtrs[(mask >> 2) & 0x3];
+        v->_curPagePtr3 = v->_pagePtrs[(mask >> 0) & 0x3];
+        video_changePal(v, v->currentPaletteId);
+    }
+}
+
+
+
+#if TRACE_FRAMEBUFFER
+
+
+uint8_t allPalettesDump[][48] = {
+
+
+    {
+        0x4, 0x4, 0x4, 0x22, 0x0, 0x0, 0x4, 0x8, 0x11, 0x4, 0xC, 0x15, 0x8, 0x11, 0x19, 0xC, 0x15, 0x1D, 0x15, 0x1D, 0x26, 0x1D, 0x2A, 0x2E, 0x2E, 0x22, 0x0, 0x3F, 0x0, 0x0, 0x33, 0x26, 0x0, 0x37, 0x2A, 0x0, 0x3B, 0x33, 0x0, 0x3F, 0x3B, 0x0, 0x3F, 0x3F, 0x1D, 0x3F, 0x3F, 0x2A,
+    },
+
+    {
+        0x4, 0x4, 0x4, 0xC, 0xC, 0x11, 0x4, 0x8, 0x11, 0x4, 0xC, 0x15, 0x8, 0x11, 0x19, 0xC, 0x15, 0x1D, 0x15, 0x1D, 0x26, 0x1D, 0x2A, 0x2E, 0x2E, 0x22, 0x0, 0x15, 0x11, 0x11, 0x33, 0x26, 0x0, 0x37, 0x2A, 0x0, 0x3B, 0x33, 0x0, 0x3F, 0x3B, 0x0, 0x3F, 0x3F, 0x1D, 0x3F, 0x3F, 0x2A,
+    }
+
+    , {
+        0x0, 0x0, 0x0, 0x22, 0x0, 0x0, 0x4, 0x8, 0x11, 0x4, 0xC, 0x15, 0x8, 0x11, 0x19, 0xC, 0x15, 0x1D, 0x15, 0x1D, 0x26, 0x1D, 0x2A, 0x2E, 0x1D, 0x1D, 0x1D, 0x15, 0x15, 0x15, 0xC, 0x8, 0xC, 0x11, 0x11, 0x15, 0x1D, 0x15, 0x15, 0x15, 0x0, 0x0, 0x0, 0x4, 0xC, 0x3F, 0x3F, 0x2A,
+    }
+
+    , {
+        0x0, 0x0, 0x0, 0x22, 0x0, 0x0, 0x4, 0x8, 0x11, 0x4, 0xC, 0x15, 0x8, 0x11, 0x19, 0xC, 0x15, 0x1D, 0x15, 0x1D, 0x26, 0x1D, 0x2A, 0x2E, 0x1D, 0x1D, 0x1D, 0x15, 0x15, 0x15, 0xC, 0x8, 0xC, 0x11, 0x11, 0x15, 0x1D, 0x15, 0x15, 0x15, 0x0, 0x0, 0x0, 0x4, 0xC, 0x3F, 0x3F, 0x2A,
+    }
+
+    , {
+        0x0, 0x0, 0x0, 0x1D, 0x0, 0x0, 0x4, 0x8, 0x11, 0x4, 0xC, 0x15, 0x8, 0x11, 0x19, 0xC, 0x15, 0x1D, 0x15, 0x1D, 0x26, 0x1D, 0x2A, 0x2E, 0x1D, 0x1D, 0x1D, 0x15, 0x15, 0x15, 0xC, 0x8, 0xC, 0x15, 0x11, 0x19, 0x1D, 0x15, 0x15, 0x15, 0x0, 0x0, 0x0, 0x4, 0xC, 0x3F, 0x3F, 0x2A,
+    }
+
+    , {
+        0x0, 0x4, 0x8, 0x15, 0x1D, 0x1D, 0x0, 0x8, 0x11, 0x4, 0xC, 0x15, 0x8, 0x11, 0x19, 0xC, 0x15, 0x1D, 0xC, 0x19, 0x22, 0x11, 0x1D, 0x26, 0x8, 0x8, 0x8, 0x0, 0x0, 0x0, 0x2E, 0x2E, 0x2E, 0xC, 0xC, 0xC, 0x15, 0xC, 0x15, 0xC, 0x15, 0x15, 0x11, 0x19, 0x19, 0x1D, 0x26, 0x26,
+    }
+
+    , {
+        0x0, 0x0, 0x0, 0x0, 0x4, 0xC, 0x4, 0x8, 0x11, 0x4, 0xC, 0x15, 0x8, 0x11, 0x19, 0xC, 0x15, 0x1D, 0x15, 0x1D, 0x26, 0x1D, 0x2A, 0x2E, 0x15, 0x0, 0x0, 0xC, 0xC, 0xC, 0xC, 0xC, 0xC, 0xC, 0x11, 0x11, 0x11, 0x11, 0x15, 0x26, 0x0, 0x0, 0x33, 0x26, 0x0, 0x3B, 0x33, 0x11,
+    }
+
+    , {
+        0x0, 0x0, 0x0, 0x0, 0x4, 0xC, 0x0, 0x8, 0x11, 0x4, 0xC, 0x15, 0x8, 0x11, 0x19, 0xC, 0x15, 0x1D, 0x15, 0x1D, 0x26, 0x1D, 0x2A, 0x2E, 0x15, 0x0, 0x0, 0xC, 0xC, 0xC, 0xC, 0xC, 0xC, 0xC, 0x11, 0x11, 0x11, 0x11, 0x15, 0x26, 0x0, 0x0, 0x26, 0x15, 0x0, 0x26, 0x1D, 0x0,
+    }
+
+    , {
+        0x0, 0x0, 0x0, 0x0, 0x4, 0xC, 0x0, 0x8, 0x11, 0x4, 0xC, 0x15, 0x8, 0x11, 0x19, 0xC, 0x15, 0x1D, 0x15, 0x1D, 0x26, 0x1D, 0x2A, 0x2E, 0x15, 0x0, 0x0, 0xC, 0xC, 0xC, 0xC, 0xC, 0xC, 0xC, 0x11, 0x11, 0x11, 0x11, 0x15, 0x26, 0x0, 0x0, 0x3F, 0x0, 0x0, 0x26, 0x1D, 0x0,
+    }
+
+    , {
+        0x0, 0x0, 0x0, 0x8, 0x4, 0xC, 0x15, 0xC, 0x11, 0x1D, 0x11, 0x0, 0xC, 0x8, 0x11, 0x2E, 0x1D, 0x0, 0x37, 0x26, 0x8, 0x3F, 0x2E, 0x0, 0x0, 0x0, 0x0, 0x11, 0xC, 0x15, 0x26, 0x15, 0x0, 0x15, 0x11, 0x19, 0x1D, 0x15, 0x1D, 0x26, 0x19, 0x19, 0x0, 0x0, 0x0, 0x3F, 0x3F, 0x3F,
+    }
+
+    , {
+        0x0, 0x0, 0x0, 0x8, 0x4, 0xC, 0x37, 0x1D, 0x1D, 0x3B, 0x2A, 0x22, 0x11, 0xC, 0x15, 0x2A, 0x0, 0x0, 0x33, 0x11, 0x0, 0x3F, 0x33, 0x1D, 0x3B, 0x19, 0x0, 0x11, 0x11, 0x19, 0x19, 0x15, 0x1D, 0x22, 0x19, 0x22, 0x2A, 0x1D, 0x26, 0x33, 0x22, 0x26, 0x37, 0x26, 0x22, 0x1D, 0x37, 0x3F,
+    }
+
+    , {
+        0x0, 0x0, 0x0, 0x0, 0x0, 0x1D, 0x4, 0x8, 0xC, 0x2A, 0x1D, 0xC, 0x3F, 0x3B, 0x26, 0x3B, 0x2A, 0x11, 0x2A, 0x0, 0x0, 0x0, 0x11, 0x15, 0x2A, 0x1D, 0x26, 0xC, 0x8, 0xC, 0x8, 0x15, 0x1D, 0x37, 0x26, 0x22, 0x33, 0x11, 0x0, 0x2E, 0x1D, 0x19, 0x22, 0x0, 0x0, 0x3F, 0x33, 0x1D,
+    }
+
+    , {
+        0x0, 0x0, 0x0, 0x0, 0x15, 0x0, 0x4, 0x8, 0xC, 0x0, 0xC, 0x11, 0x8, 0x11, 0x19, 0x0, 0x19, 0x22, 0x2A, 0x0, 0x0, 0x19, 0x15, 0x22, 0x2E, 0x26, 0x2E, 0xC, 0x8, 0xC, 0x0, 0x2E, 0x0, 0x37, 0x26, 0x22, 0x33, 0x11, 0x0, 0x2E, 0x1D, 0x19, 0x1D, 0x0, 0x0, 0x3F, 0x3F, 0x19,
+    }
+
+    , {
+        0x0, 0x0, 0x0, 0x8, 0xC, 0x11, 0x11, 0x11, 0x15, 0x19, 0x15, 0x22, 0x26, 0x19, 0x2A, 0x2E, 0x26, 0x2E, 0x4, 0x4, 0xC, 0x0, 0xC, 0x15, 0x2A, 0x1D, 0x26, 0x0, 0x19, 0x0, 0x0, 0x2A, 0x0, 0x37, 0x26, 0x22, 0x0, 0x15, 0x1D, 0x37, 0x2E, 0x1D, 0x3F, 0x3F, 0x2E, 0x37, 0x37, 0x26,
+    }
+
+    , {
+        0x0, 0x0, 0x0, 0x0, 0x15, 0x0, 0x4, 0x8, 0xC, 0x0, 0xC, 0x11, 0x8, 0x11, 0x19, 0x0, 0x19, 0x22, 0x2A, 0x0, 0x0, 0x19, 0x15, 0x22, 0x2E, 0x26, 0x2E, 0xC, 0x8, 0xC, 0x0, 0x2E, 0x0, 0x37, 0x26, 0x22, 0x33, 0x11, 0x0, 0x2E, 0x1D, 0x19, 0x1D, 0x0, 0x0, 0x3F, 0x3F, 0x19,
+    }
+
+    , {
+        0x0, 0x0, 0x0, 0x0, 0xC, 0x0, 0x8, 0x0, 0x4, 0xC, 0x8, 0xC, 0x19, 0x11, 0x11, 0x3F, 0x3F, 0x19, 0x0, 0x1D, 0x0, 0x0, 0x3F, 0x0, 0x0, 0x4, 0x8, 0x11, 0x19, 0x11, 0x19, 0x0, 0x0, 0x2E, 0x0, 0x0, 0x3F, 0x0, 0x0, 0x37, 0x1D, 0x15, 0x0, 0x11, 0x11, 0x0, 0x3F, 0x2A,
+    }
+
+    , {
+        0x0, 0x0, 0x0, 0x0, 0xC, 0x0, 0x0, 0x11, 0x0, 0x0, 0x19, 0x0, 0xC, 0x26, 0x0, 0x15, 0x2E, 0x0, 0x4, 0x8, 0x0, 0x26, 0x11, 0x0, 0x2E, 0x3F, 0x0, 0x0, 0x15, 0x0, 0xC, 0x1D, 0x0, 0x15, 0x2E, 0x0, 0x15, 0x37, 0x0, 0x1D, 0x3F, 0x0, 0xC, 0x1D, 0xC, 0x\
+        1D, 0x26, 0x15,
+    }
+};
+
+
+
+#include "png.h"
+int GL_FCS_SaveAsSpecifiedPNG(char* path, uint8_t* pixels, int depth = 8, int format = PNG_COLOR_TYPE_RGB)
+{
+    png_structp png_ptr = NULL;
+    png_infop info_ptr = NULL;
+    png_byte ** row_pointers = NULL;
+    int status = -1;
+    int bytePerPixel = 0;
+    int y;
+
+    int fd = open (path, "wb");
+    if (fd < 0) {
+        goto fopen_failed;
+    }
+
+    png_ptr = png_create_write_struct (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
+    if (png_ptr == NULL) {
+        goto png_create_write_struct_failed;
+    }
+
+    info_ptr = png_create_info_struct (png_ptr);
+    if (info_ptr == NULL) {
+        goto png_create_info_struct_failed;
+    }
+
+    if (setjmp (png_jmpbuf (png_ptr))) {
+        goto png_failure;
+    }
+
+    /* Set image attributes. */
+
+    png_set_IHDR (png_ptr,
+                  info_ptr,
+                  640,
+                  400,
+                  depth,
+                  format,
+                  PNG_INTERLACE_NONE,
+                  PNG_COMPRESSION_TYPE_DEFAULT,
+                  PNG_FILTER_TYPE_DEFAULT);
+
+    if (format == PNG_COLOR_TYPE_GRAY   )
+        bytePerPixel = depth / 8 * 1;
+    else
+        bytePerPixel = depth / 8 * 3;
+
+    row_pointers = (png_byte **)png_malloc (png_ptr, 400 * sizeof (png_byte *));
+    //for (y = vid.height-1; y >=0; --y)
+    for (y = 0; y < 400; y++)
+    {
+        row_pointers[y] = (png_byte*)&pixels[640 * (400 - y) * bytePerPixel];
+    }
+
+    png_init_io (png_ptr, fp);
+    png_set_rows (png_ptr, info_ptr, row_pointers);
+    png_write_png (png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL);
+    //png_read_image (png_ptr, info_ptr);//
+
+    status = 0;
+
+    png_free (png_ptr, row_pointers);
+
+
+png_failure:
+png_create_info_struct_failed:
+    png_destroy_write_struct (&png_ptr, &info_ptr);
+
+png_create_write_struct_failed:
+    fclose (fp);
+fopen_failed:
+    return status;
+}
+
+void writeLine(uint8_t *dst, uint8_t *src, int size)
+{
+    uint8_t* dumpPalette;
+
+    if (!dumpPaletteCursor)
+        dumpPalette = allPalettesDump[dumpPaletteCursor];
+    else
+        dumpPalette = pal;
+
+    for( uint8_t twoPixels = 0 ; twoPixels < size ; twoPixels++)
+    {
+        int pixelIndex0 = (*src & 0xF0) >> 4;
+        pixelIndex0 &= 0x10 - 1;
+
+        int pixelIndex1 = (*src & 0xF);
+        pixelIndex1 &= 0x10 - 1;
+
+        //We need to write those two pixels
+        dst[0] = dumpPalette[pixelIndex0 * 3] << 2 | dumpPalette[pixelIndex0 * 3];
+        dst[1] = dumpPalette[pixelIndex0 * 3 + 1] << 2 | dumpPalette[pixelIndex0 * 3 + 1];
+        dst[2] = dumpPalette[pixelIndex0 * 3 + 2] << 2 | dumpPalette[pixelIndex0 * 3 + 2];
+        //dst[3] = 0xFF;
+        dst += SCREENSHOT_BPP;
+
+        dst[0] = dumpPalette[pixelIndex1 * 3] << 2 | dumpPalette[pixelIndex1 * 3];
+        dst[1] = dumpPalette[pixelIndex1 * 3 + 1] << 2 | dumpPalette[pixelIndex1 * 3 + 1];
+        dst[2] = dumpPalette[pixelIndex1 * 3 + 2] << 2 | dumpPalette[pixelIndex1 * 3 + 2];
+        //dst[3] = 0xFF;
+        dst += SCREENSHOT_BPP;
+
+        src++;
+    }
+}
+
+void video_dumpFrameBuffer(uint8_t *src, uint8_t *dst, int x, int y)
+{
+
+    for (int line = 199 ; line >= 0 ; line--)
+    {
+        writeLine(dst + x * SCREENSHOT_BPP + y * 640 * SCREENSHOT_BPP  , src + line * 160, 160);
+        dst += 640 * SCREENSHOT_BPP;
+    }
+}
+
+void video_dumpFrameBuffers(char* comment)
+{
+
+    if (!traceFrameBufferCounter)
+    {
+        rb->memset(allFrameBuffers, 0, sizeof(allFrameBuffers));
+    }
+
+
+    dumpFrameBuffer(v->_pagePtrs[1], allFrameBuffers, 0, 0);
+    dumpFrameBuffer(v->_pagePtrs[0], allFrameBuffers, 0, 200);
+    dumpFrameBuffer(v->_pagePtrs[2], allFrameBuffers, 320, 0);
+    //dumpFrameBuffer(v->_pagePtrs[3],allFrameBuffers,320,200);
+
+
+    //if (v->_curPagePtr1 == v->_pagePtrs[3])
+    //
+
+    /*
+      uint8_t* offScreen = sys->getOffScreenFramebuffer();
+      for(int i=0 ; i < 200 ; i++)
+      writeLine(allFrameBuffers+320*3+640*i*3 + 200*640*3,  offScreen+320*i/2  ,  160);
+    */
+
+
+    int frameId = traceFrameBufferCounter++;
+    //Write bitmap to disk.
+
+
+
+    // Filling TGA header information
+    /*
+      char path[256];
+      sprintf(path,"test%d.tga",traceFrameBufferCounter);
+
+      #define IMAGE_WIDTH 640
+      #define IMAGE_HEIGHT 400
+
+      uint8_t tga_header[18];
+      rb->memset(tga_header, 0, 18);
+      tga_header[2] = 2;
+      tga_header[12] = (IMAGE_WIDTH & 0x00FF);
+      tga_header[13] = (IMAGE_WIDTH  & 0xFF00) / 256;
+      tga_header[14] = (IMAGE_HEIGHT  & 0x00FF) ;
+      tga_header[15] =(IMAGE_HEIGHT & 0xFF00) / 256;
+      tga_header[16] = 32 ;
+
+
+
+      // Open the file, write both header and payload, close, done.
+      char path[256];
+      sprintf(path,"test%d.tga",traceFrameBufferCounter);
+      FILE* pScreenshot = fopen(path, "wb");
+      fwrite(&tga_header, 18, sizeof(uint8_t), pScreenshot);
+      fwrite(allFrameBuffers, IMAGE_WIDTH * IMAGE_HEIGHT,SCREENSHOT_BPP * sizeof(uint8_t),pScreenshot);
+      fclose(pScreenshot);
+    */
+
+
+    char path[256];
+    //sprintf(path,"%4d%s.png",traceFrameBufferCounter,comment);
+    sprintf(path, "%4d.png", traceFrameBufferCounter);
+
+    GL_FCS_SaveAsSpecifiedPNG(path, allFrameBuffers);
+}
+#endif
+
+#if TRACE_BG_BUFFER
+
+
+
+uint8_t bgPalette[48] = {
+    0x8, 0x8, 0xC, 0xC, 0xC, 0x15, 0xC, 0x11, 0x1D, 0x15, 0x2A, 0x3F, 0x1D, 0x19, 0x19, 0x37, 0x2E, 0x2A, 0x26, 0x1D, 0x1D, 0x37, 0x26, 0x22, 0x22, 0xC, 0x0, 0x26, 0x33, 0x3F, 0x11, 0x11, 0x15, 0x11, 0x15, 0x1D, 0x15, 0x19, 0x26, 0x15, 0x1D, 0x37, 0x0, 0x26, 0x3F, 0x2E, 0x15, 0x0,
+};
+void bgWriteLine(uint8_t *dst, uint8_t *src, int size)
+{
+    uint8_t* dumpPalette;
+
+//      if (!dumpPaletteCursor)
+    //  dumpPalette = allPalettesDump[dumpPaletteCursor];
+//      else
+    dumpPalette = bgPalette;
+
+    for( uint8_t twoPixels = 0 ; twoPixels < size ; twoPixels++)
+    {
+        int pixelIndex0 = (*src & 0xF0) >> 4;
+        pixelIndex0 &= 0x10 - 1;
+
+        int pixelIndex1 = (*src & 0xF);
+        pixelIndex1 &= 0x10 - 1;
+
+        //We need to write those two pixels
+        dst[0] = dumpPalette[pixelIndex0 * 3] << 2 | dumpPalette[pixelIndex0 * 3];
+        dst[1] = dumpPalette[pixelIndex0 * 3 + 1] << 2 | dumpPalette[pixelIndex0 * 3 + 1];
+        dst[2] = dumpPalette[pixelIndex0 * 3 + 2] << 2 | dumpPalette[pixelIndex0 * 3 + 2];
+        //dst[3] = 0xFF;
+        dst += 3;
+
+        dst[0] = dumpPalette[pixelIndex1 * 3] << 2 | dumpPalette[pixelIndex1 * 3];
+        dst[1] = dumpPalette[pixelIndex1 * 3 + 1] << 2 | dumpPalette[pixelIndex1 * 3 + 1];
+        dst[2] = dumpPalette[pixelIndex1 * 3 + 2] << 2 | dumpPalette[pixelIndex1 * 3 + 2];
+        //dst[3] = 0xFF;
+        dst += 3;
+
+        src++;
+    }
+}
+
+void bgDumpFrameBuffer(uint8_t *src, uint8_t *dst, int x, int y)
+{
+
+    for (int line = 199 ; line >= 0 ; line--)
+    {
+        bgWriteLine(dst + x * 3 + y * 320 * 3  , src + line * 160, 160);
+        dst += 320 * 3;
+    }
+}
+
+#include "png.h"
+int bgSaveAsSpecifiedPNG(char* path, uint8_t* pixels, int depth = 8, int format = PNG_COLOR_TYPE_RGB)
+{
+#if 0
+    FILE * fp;
+    png_structp png_ptr = NULL;
+    png_infop info_ptr = NULL;
+    png_byte ** row_pointers = NULL;
+    int status = -1;
+    int bytePerPixel = 0;
+    int y;
+
+    fp = fopen (path, "wb");
+    if (! fp) {
+        goto fopen_failed;
+    }
+
+    png_ptr = png_create_write_struct (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
+    if (png_ptr == NULL) {
+        goto png_create_write_struct_failed;
+    }
+
+    info_ptr = png_create_info_struct (png_ptr);
+    if (info_ptr == NULL) {
+        goto png_create_info_struct_failed;
+    }
+
+    if (setjmp (png_jmpbuf (png_ptr))) {
+        goto png_failure;
+    }
+
+    /* Set image attributes. */
+
+    png_set_IHDR (png_ptr,
+                  info_ptr,
+                  320,
+                  200,
+                  depth,
+                  format,
+                  PNG_INTERLACE_NONE,
+                  PNG_COMPRESSION_TYPE_DEFAULT,
+                  PNG_FILTER_TYPE_DEFAULT);
+
+    if (format == PNG_COLOR_TYPE_GRAY   )
+        bytePerPixel = depth / 8 * 1;
+    else
+        bytePerPixel = depth / 8 * 3;
+
+    row_pointers = (png_byte **)png_malloc (png_ptr, 200 * sizeof (png_byte *));
+    //for (y = vid.height-1; y >=0; --y)
+    for (y = 0; y < 200; y++)
+    {
+        row_pointers[y] = (png_byte*)&pixels[320 * (200 - y) * bytePerPixel];
+    }
+
+    png_init_io (png_ptr, fp);
+    png_set_rows (png_ptr, info_ptr, row_pointers);
+    png_write_png (png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL);
+    //png_read_image (png_ptr, info_ptr);//
+
+    status = 0;
+
+    png_free (png_ptr, row_pointers);
+
+
+png_failure:
+png_create_info_struct_failed:
+    png_destroy_write_struct (&png_ptr, &info_ptr);
+
+png_create_write_struct_failed:
+    fclose (fp);
+fopen_failed:
+    return status;
+#endif
+}
+
+int bgFrameBufferCounter = 0;
+
+void video_dumpBackGroundBuffer()
+{
+    if (v->_curPagePtr1 != v->_pagePtrs[0])
+        return;
+
+    uint8_t bgBuffer[320 * 200 * 3];
+    bgDumpFrameBuffer(v->_curPagePtr1, bgBuffer, 0, 0);
+
+
+    char path[256];
+    //sprintf(path,"%4d%s.png",traceFrameBufferCounter,comment);
+    sprintf(path, "bg%4d.png", bgFrameBufferCounter++);
+
+    bgSaveAsSpecifiedPNG(path, bgBuffer);
+}
+
+#endif
diff --git a/apps/plugins/xworld/video.h b/apps/plugins/xworld/video.h
new file mode 100644
index 0000000..d4ff64f
--- /dev/null
+++ b/apps/plugins/xworld/video.h
@@ -0,0 +1,127 @@
+/***************************************************************************
+ *             __________               __   ___.
+ *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
+ *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
+ *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
+ *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
+ *                     \/            \/     \/    \/            \/
+ * $Id$
+ *
+ * Copyright (C) 2014 Franklin Wei, Benjamin Brown
+ * Copyright (C) 2004 Gregory Montoir
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ***************************************************************************/
+
+#ifndef __VIDEO_H__
+#define __VIDEO_H__
+
+#include "intern.h"
+
+struct StrEntry {
+    uint16_t id;
+    char *str;
+};
+#define MAX_POINTS 50
+struct Polygon {
+
+    uint16_t bbw, bbh;
+    uint8_t numPoints;
+    struct Point points[MAX_POINTS];
+
+};
+void polygon_readVertices(struct Polygon*, const uint8_t *p, uint16_t zoom);
+
+struct Resource;
+struct Serializer;
+struct System;
+
+// This is used to detect the end of  _stringsTableEng and _stringsTableDemo
+#define END_OF_STRING_DICTIONARY 0xFFFF
+
+// Special value when no palette change is necessary
+#define NO_PALETTE_CHANGE_REQUESTED 0xFF
+
+/* 320x200 pixels, with 2 pixels/byte */
+#define VID_PAGE_SIZE ( 320 * 200 / 2 )
+
+struct Video {
+
+    /* FW: static const uint8_t _font[];*/
+    /* FW: moved to video_data.c */
+    struct Resource *res;
+    struct System *sys;
+
+
+
+    uint8_t paletteIdRequested, currentPaletteId;
+    uint8_t *_pagePtrs[4];
+    uint8_t *page_data;
+    // I am almost sure that:
+    // _curPagePtr1 is the backbuffer
+    // _curPagePtr2 is the frontbuffer
+    // _curPagePtr3 is the background builder.
+    uint8_t *_curPagePtr1, *_curPagePtr2, *_curPagePtr3;
+
+    struct Polygon polygon;
+    int16_t _hliney;
+
+    //Precomputer division lookup table
+    uint16_t _interpTable[0x400];
+
+    struct Ptr _pData;
+    uint8_t *_dataBuf;
+
+
+};
+
+typedef void (*drawLine)(struct Video*, int16_t x1, int16_t x2, uint8_t col);
+
+//Video(Resource *res, System *stub);
+void video_create(struct Video*, struct Resource*, struct System*);
+void video_init(struct Video* v);
+
+void video_setDataBuffer(struct Video* v, uint8_t *dataBuf, uint16_t offset);
+void video_readAndDrawPolygon(struct Video* v, uint8_t color, uint16_t zoom, const struct Point *pt);
+void video_fillPolygon(struct Video* v, uint16_t color, uint16_t zoom, const struct Point *pt);
+void video_readAndDrawPolygonHierarchy(struct Video* v, uint16_t zoom, const struct Point *pt);
+int32_t video_calcStep(struct Video* v, const struct Point *p1, const struct Point *p2, uint16_t *dy);
+
+void video_drawString(struct Video* v, uint8_t color, uint16_t x, uint16_t y, uint16_t strId);
+void video_drawChar(struct Video* v, uint8_t c, uint16_t x, uint16_t y, uint8_t color, uint8_t *buf);
+void video_drawPoint(struct Video* v, uint8_t color, int16_t x, int16_t y);
+void video_drawLineBlend(struct Video* v, int16_t x1, int16_t x2, uint8_t color);
+void video_drawLineN(struct Video* v, int16_t x1, int16_t x2, uint8_t color);
+void video_drawLineP(struct Video* v, int16_t x1, int16_t x2, uint8_t color);
+uint8_t *video_getPagePtr(struct Video* v, uint8_t page);
+void video_changePagePtr1(struct Video* v, uint8_t page);
+void video_fillPage(struct Video* v, uint8_t page, uint8_t color);
+void video_copyPage(struct Video* v, uint8_t src, uint8_t dst, int16_t vscroll);
+void video_copyPagePtr(struct Video* v, const uint8_t *src);
+uint8_t *video_allocPage(struct Video* v);
+void video_changePal(struct Video* v, uint8_t pal);
+void video_updateDisplay(struct Video* v, uint8_t page);
+
+void video_saveOrLoad(struct Video* v, struct Serializer *ser);
+
+#define TRACE_PALETTE 0
+#define TRACE_FRAMEBUFFER 0
+#if TRACE_FRAMEBUFFER
+void video_dumpFrameBuffer(struct Video* v, uint8_t *src, uint8_t *dst, int x, int y);
+void video_dumpFrameBuffers(struct Video* v, char* comment);
+
+#endif
+
+#define TRACE_BG_BUFFER 0
+#if TRACE_BG_BUFFER
+void video_dumpBackGroundBuffer(struct Video* v);
+#endif
+
+#endif
diff --git a/apps/plugins/xworld/video_data.c b/apps/plugins/xworld/video_data.c
new file mode 100644
index 0000000..e658c17
--- /dev/null
+++ b/apps/plugins/xworld/video_data.c
@@ -0,0 +1,271 @@
+/***************************************************************************
+ *             __________               __   ___.
+ *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
+ *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
+ *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
+ *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
+ *                     \/            \/     \/    \/            \/
+ * $Id$
+ *
+ * Copyright (C) 2014 Franklin Wei, Benjamin Brown
+ * Copyright (C) 2004 Gregory Montoir
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ***************************************************************************/
+
+#include "video.h"
+#include "video_data.h"
+#include "stdint.h"
+
+/* this font is based off 10-Fixed.bdf with lowercase characters
+   from 09-Fixed.bdf and a handcrafted copyright symbol */
+
+uint8_t video_font[FONT_SIZE] = {
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* ' ' */
+    0x20, 0x20, 0x20, 0x20, 0x20, 0x00, 0x20, 0x00, /* '!' */
+    0x50, 0x50, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, /* '"' */
+    0x50, 0x50, 0xF8, 0x50, 0xF8, 0x50, 0x50, 0x00, /* '#' */
+    0x20, 0x70, 0xA0, 0x70, 0x28, 0x70, 0x20, 0x00, /* '$' */
+    0x48, 0xA8, 0x50, 0x20, 0x50, 0xA8, 0x90, 0x00, /* '%' */
+    0x40, 0xA0, 0xA0, 0x40, 0xA8, 0x90, 0x68, 0x00, /* '&' */
+    0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, /* ''' */
+    0x10, 0x20, 0x40, 0x40, 0x40, 0x20, 0x10, 0x00, /* '(' */
+    0x40, 0x20, 0x10, 0x10, 0x10, 0x20, 0x40, 0x00, /* ')' */
+    0x00, 0x88, 0x50, 0xF8, 0x50, 0x88, 0x00, 0x00, /* '*' */
+    0x00, 0x20, 0x20, 0xF8, 0x20, 0x20, 0x00, 0x00, /* '+' */
+    0x00, 0x00, 0x00, 0x00, 0x30, 0x20, 0x40, 0x00, /* ',' */
+    0x00, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x00, 0x00, /* '-' */
+    0x00, 0x00, 0x00, 0x00, 0x20, 0x50, 0x20, 0x00, /* '.' */
+    0x08, 0x08, 0x10, 0x20, 0x40, 0x80, 0x80, 0x00, /* '/' */
+    0x20, 0x50, 0x88, 0x88, 0x88, 0x50, 0x20, 0x00, /* '0' */
+    0x20, 0x60, 0xA0, 0x20, 0x20, 0x20, 0xF8, 0x00, /* '1' */
+    0x70, 0x88, 0x08, 0x30, 0x40, 0x80, 0xF8, 0x00, /* '2' */
+    0xF8, 0x08, 0x10, 0x30, 0x08, 0x88, 0x70, 0x00, /* '3' */
+    0x10, 0x30, 0x50, 0x90, 0xF8, 0x10, 0x10, 0x00, /* '4' */
+    0xF8, 0x80, 0xB0, 0xC8, 0x08, 0x88, 0x70, 0x00, /* '5' */
+    0x30, 0x40, 0x80, 0xB0, 0xC8, 0x88, 0x70, 0x00, /* '6' */
+    0xF8, 0x08, 0x10, 0x10, 0x20, 0x40, 0x40, 0x00, /* '7' */
+    0x70, 0x88, 0x88, 0x70, 0x88, 0x88, 0x70, 0x00, /* '8' */
+    0x70, 0x88, 0x98, 0x68, 0x08, 0x10, 0x60, 0x00, /* '9' */
+    0x00, 0x60, 0x60, 0x00, 0x60, 0x60, 0x00, 0x00, /* ':' */
+    0x00, 0x30, 0x30, 0x00, 0x30, 0x20, 0x40, 0x00, /* ';' */
+    0x08, 0x10, 0x20, 0x40, 0x20, 0x10, 0x08, 0x00, /* '<' */
+    0x00, 0x00, 0xF8, 0x00, 0xF8, 0x00, 0x00, 0x00, /* '=' */
+    0x40, 0x20, 0x10, 0x08, 0x10, 0x20, 0x40, 0x00, /* '>' */
+    0x70, 0x88, 0x10, 0x20, 0x20, 0x00, 0x20, 0x00, /* '?' */
+    0x40, 0x20, 0x10, 0x10, 0x10, 0x20, 0x40, 0x00, /* ')' */
+    0x20, 0x50, 0x88, 0x88, 0xF8, 0x88, 0x88, 0x00, /* 'A' */
+    0xF0, 0x88, 0x88, 0xF0, 0x88, 0x88, 0xF0, 0x00, /* 'B' */
+    0x70, 0x88, 0x80, 0x80, 0x80, 0x88, 0x70, 0x00, /* 'C' */
+    0xF0, 0x88, 0x88, 0x88, 0x88, 0x88, 0xF0, 0x00, /* 'D' */
+    0xF8, 0x80, 0x80, 0xF0, 0x80, 0x80, 0xF8, 0x00, /* 'E' */
+    0xF8, 0x80, 0x80, 0xF0, 0x80, 0x80, 0x80, 0x00, /* 'F' */
+    0x70, 0x88, 0x80, 0x80, 0x98, 0x88, 0x70, 0x00, /* 'G' */
+    0x88, 0x88, 0x88, 0xF8, 0x88, 0x88, 0x88, 0x00, /* 'H' */
+    0x70, 0x20, 0x20, 0x20, 0x20, 0x20, 0x70, 0x00, /* 'I' */
+    0x38, 0x10, 0x10, 0x10, 0x10, 0x90, 0x60, 0x00, /* 'J' */
+    0x88, 0x90, 0xA0, 0xC0, 0xA0, 0x90, 0x88, 0x00, /* 'K' */
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xF8, 0x00, /* 'L' */
+    0x88, 0x88, 0xD8, 0xA8, 0x88, 0x88, 0x88, 0x00, /* 'M' */
+    0x88, 0x88, 0xC8, 0xA8, 0x98, 0x88, 0x88, 0x00, /* 'N' */
+    0x70, 0x88, 0x88, 0x88, 0x88, 0x88, 0x70, 0x00, /* 'O' */
+    0xF0, 0x88, 0x88, 0xF0, 0x80, 0x80, 0x80, 0x00, /* 'P' */
+    0x70, 0x88, 0x88, 0x88, 0x88, 0xA8, 0x70, 0x00, /* 'Q' */
+    0xF0, 0x88, 0x88, 0xF0, 0xA0, 0x90, 0x88, 0x00, /* 'R' */
+    0x70, 0x88, 0x80, 0x70, 0x08, 0x88, 0x70, 0x00, /* 'S' */
+    0xF8, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x00, /* 'T' */
+    0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x70, 0x00, /* 'U' */
+    0x88, 0x88, 0x88, 0x50, 0x50, 0x50, 0x20, 0x00, /* 'V' */
+    0x88, 0x88, 0x88, 0xA8, 0xA8, 0xD8, 0x88, 0x00, /* 'W' */
+    0x88, 0x88, 0x50, 0x20, 0x50, 0x88, 0x88, 0x00, /* 'X' */
+    0x88, 0x88, 0x50, 0x20, 0x20, 0x20, 0x20, 0x00, /* 'Y' */
+    0xF8, 0x08, 0x10, 0x20, 0x40, 0x80, 0xF8, 0x00, /* 'Z' */
+    0x70, 0x40, 0x40, 0x40, 0x40, 0x70, 0x00, 0x00, /* '[' */
+    0x80, 0x80, 0x40, 0x20, 0x10, 0x10, 0x00, 0x00, /* '\' */
+    0x70, 0x10, 0x10, 0x10, 0x10, 0x70, 0x00, 0x00, /* ']' */
+    0x20, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* '^' */
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x00, /* '_' */
+    0x40, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* '`' */
+    0x00, 0x00, 0x70, 0x08, 0x78, 0x88, 0x78, 0x00, /* 'a' */
+    0x80, 0x80, 0xB0, 0xC8, 0x88, 0xC8, 0xB0, 0x00, /* 'b' */
+    0x00, 0x00, 0x70, 0x88, 0x80, 0x88, 0x70, 0x00, /* 'c' */
+    0x08, 0x08, 0x68, 0x98, 0x88, 0x98, 0x68, 0x00, /* 'd' */
+    0x00, 0x00, 0x70, 0x88, 0xF8, 0x80, 0x70, 0x00, /* 'e' */
+    0x30, 0x48, 0x40, 0xF0, 0x40, 0x40, 0x40, 0x00, /* 'f' */
+    0x00, 0x00, 0x60, 0x90, 0x90, 0x70, 0x10, 0x60, /* 'g' */
+    0x80, 0x80, 0xB0, 0xC8, 0x88, 0x88, 0x88, 0x00, /* 'h' */
+    0x20, 0x00, 0x60, 0x20, 0x20, 0x20, 0x70, 0x00, /* 'i' */
+    0x20, 0x00, 0x60, 0x20, 0x20, 0x20, 0xA0, 0x40, /* 'j' */
+    0x80, 0x80, 0x88, 0x90, 0xE0, 0x90, 0x88, 0x00, /* 'k' */
+    0x60, 0x20, 0x20, 0x20, 0x20, 0x20, 0x70, 0x00, /* 'l' */
+    0x00, 0x00, 0xD0, 0xA8, 0xA8, 0xA8, 0x88, 0x00, /* 'm' */
+    0x00, 0x00, 0xB0, 0xC8, 0x88, 0x88, 0x88, 0x00, /* 'n' */
+    0x00, 0x00, 0x70, 0x88, 0x88, 0x88, 0x70, 0x00, /* 'o' */
+    0x00, 0x00, 0xE0, 0x90, 0x90, 0xE0, 0x80, 0x80, /* 'p' */
+    0x00, 0x00, 0x70, 0x90, 0x90, 0x70, 0x10, 0x10, /* 'q' */
+    0x00, 0x00, 0xB0, 0xC8, 0x80, 0x80, 0x80, 0x00, /* 'r' */
+    0x00, 0x00, 0x70, 0x80, 0x70, 0x08, 0xF0, 0x00, /* 's' */
+    0x40, 0x40, 0xF0, 0x40, 0x40, 0x48, 0x30, 0x00, /* 't' */
+    0x00, 0x00, 0x88, 0x88, 0x88, 0x98, 0x68, 0x00, /* 'u' */
+    0x00, 0x00, 0x88, 0x88, 0x50, 0x50, 0x20, 0x00, /* 'v' */
+    0x00, 0x00, 0x88, 0x88, 0xA8, 0xA8, 0x50, 0x00, /* 'w' */
+    0x00, 0x00, 0x88, 0x50, 0x20, 0x50, 0x88, 0x00, /* 'x' */
+    0x00, 0x00, 0x90, 0x90, 0x90, 0x70, 0x90, 0x60, /* 'y' */
+    0x00, 0x00, 0xF8, 0x10, 0x20, 0x40, 0xF8, 0x00, /* 'z' */
+    0x18, 0x20, 0x10, 0x60, 0x10, 0x20, 0x18, 0x00, /* '{' */
+    0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0x00, /* cursor */
+    0x38, 0x54, 0xAA, 0xA2, 0xAA, 0x54, 0x38, 0x00, /* copyright symbol */
+    0x70, 0x88, 0x88, 0x88, 0x88, 0x50, 0xD8, 0x00, /* omega */
+    0x00, 0xA0, 0x10, 0x80, 0x10, 0x80, 0x50, 0x00, /* DEL */
+};
+
+struct StrEntry video_stringsTableEng[MAX_STRING_TABLE_SIZE] = {
+    { 0x001, "B A N A N A  2000" },
+    { 0x002, "Copyright  } 2014 Banana Corporation \nGPLv2\n\nBUNIX Revision 3.14" },
+    { 0x003, "1" },
+    { 0x004, "3" },
+    { 0x005, "." },
+    { 0x006, "a" },
+    { 0x007, "@" },
+    { 0x008, "BANANA 2000" },
+    { 0x00A, "R" },
+    { 0x00B, "U" },
+    { 0x00C, "N" },
+    { 0x00D, "P" },
+    { 0x00E, "R" },
+    { 0x00F, "O" },
+    { 0x010, "J" },
+    { 0x011, "E" },
+    { 0x012, "C" },
+    { 0x013, "T" },
+    { 0x014, "Fields 100.05Mf OK" },
+    { 0x015, "Lines of Flux % 14.077 OK" },
+    { 0x016, "IONS  OK" },
+    { 0x017, " %%%ddd OK" },
+    { 0x018, "TEMP ok" },
+    { 0x019, "EXECUTE" },
+    { 0x01A, "V= 24%\nG: 1.05\n\nMG: 177.2l\n\nOPT: G>\n\n Field:\nI: OFF\nII: ON\nIII: ON\n\np~: I\n" },
+    { 0x01B, "on" },
+    { 0x01C, "-" },
+    { 0x021, "|" },
+    { 0x022, "--- Simulation ---" },
+    { 0x023, "           TEST WILL START IN    SECONDS" },
+    { 0x024, "  20" },
+    { 0x025, "  19" },
+    { 0x026, "  18" },
+    { 0x027, "  4" },
+    { 0x028, "  3" },
+    { 0x029, "  2" },
+    { 0x02A, "  1" },
+    { 0x02B, "  0" },
+    { 0x02C, "C A U T I O N" },
+    { 0x031, "- Test 0:\nGenerate electron beam\n" },
+    { 0x032, "- Test 1:\nCalculating flux coefficient\n" },
+    { 0x033, "- Test 2:\nIncrease magnetic field\n" },
+    { 0x034, "R E S U L T S" },
+    { 0x035, "- NOTE:\nChances of producing:\n Anti-matter: 34 %\n Neutrino 71:  4 %\n Positron 34: 99 %\n" },
+    { 0x036, "   Continue Test y/n ?" },
+    { 0x037, "Are You Sure?" },
+    { 0x038, "Setting Configuration\n of accelerator\n'Verified'" },
+    { 0x039, "       Continue ?" },
+    { 0x03C, "T___T" },
+    { 0x03D, "OOO ~" },
+    { 0x03E, ".40X13DD" },
+    { 0x03F, "ferfxwre" },
+    { 0x040, "Trfor 25%" },
+    { 0x041, "32% 56% GOOD" },
+    { 0x042, "E=2.7182818289" },
+    { 0x043, "G=330.01" },
+    { 0x044, "+" },
+    { 0x045, "*" },
+    { 0x046, "% 234" },
+    { 0x047, "Gorwle 12" },
+    { 0x048, "[[[[" },
+    { 0x049, "Elephine Soft" },
+    { 0x04A, "By Many talented People" },
+    { 0x04B, "  4" },
+    { 0x04C, "  16" },
+    { 0x12C, "0" },
+    { 0x12D, "1" },
+    { 0x12E, "2" },
+    { 0x12F, "3" },
+    { 0x130, "4" },
+    { 0x131, "5" },
+    { 0x132, "6" },
+    { 0x133, "7" },
+    { 0x134, "8" },
+    { 0x135, "9" },
+    { 0x136, "A" },
+    { 0x137, "B" },
+    { 0x138, "C" },
+    { 0x139, "D" },
+    { 0x13A, "E" },
+    { 0x13B, "F" },
+    { 0x13C, "        LEVEL CODE:" },
+    { 0x13D, "    PRESS ANY KEY TO CONTINUE" },
+    { 0x13E, "   ENTER CODE" },
+    { 0x13F, "   CODE NOT VALID!!" },
+    { 0x140, "AN NULER" },
+    { 0x141, "      ??????\n\n\n\n\n\n\n\n\nANY KEY TO CONTINUE" },
+    { 0x142, " ENTER THE CODE CORRELATING TO\n POSITION\n ON THE DECODER WHEEL" },
+    { 0x143, "    LOAD..." },
+    { 0x144, "              ERROR" },
+    { 0x15E, "LDKD" },
+    { 0x15F, "HTDC" },
+    { 0x160, "CLLD" },
+    { 0x161, "FXLC" },
+    { 0x162, "KRFK" },
+    { 0x163, "XDDJ" },
+    { 0x164, "LBKG" },
+    { 0x165, "KLFB" },
+    { 0x166, "TTCT" },
+    { 0x167, "DDRX" },
+    { 0x168, "TBHK" },
+    { 0x169, "BRTD" },
+    { 0x16A, "CKJL" },
+    { 0x16B, "LFCK" },
+    { 0x16C, "BFLX" },
+    { 0x16D, "XJRT" },
+    { 0x16E, "HRTB" },
+    { 0x16F, "HBHK" },
+    { 0x170, "JCGB" },
+    { 0x171, "HHFL" },
+    { 0x172, "TFBB" },
+    { 0x173, "TXHF" },
+    { 0x174, "JHJL" },
+    { 0x181, " " },
+    { 0x182, " " },
+    { 0x183, " " },
+    { 0x184, " " },
+    { 0x185, " " },
+    { 0x186, " " },
+    { 0x187, " " },
+    { 0x188, " " },
+    { 0x18B, " " },
+    { 0x18C, " " },
+    { 0x18D, " " },
+    { 0x18E, " " },
+    { 0x258, " " },
+    { 0x259, " " },
+    { 0x25A, " " },
+    { 0x25B, " " },
+    { 0x25C, " " },
+    { 0x25D, " " },
+    { 0x263, " " },
+    { 0x264, " " },
+    { 0x265, " " },
+    { 0x190, "Hello Master." },
+    { 0x191, "Identifiy confirmed.\nAccess granted." },
+    { 0x192, " ACCESSING" },
+    { 0x193, " " },
+    { 0x194, "y\n" },
+    { 0x193, "!!!\n" },
+    { END_OF_STRING_DICTIONARY, "" }
+};
diff --git a/apps/plugins/xworld/video_data.h b/apps/plugins/xworld/video_data.h
new file mode 100644
index 0000000..cd53205
--- /dev/null
+++ b/apps/plugins/xworld/video_data.h
@@ -0,0 +1,29 @@
+/***************************************************************************
+ *             __________               __   ___.
+ *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
+ *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
+ *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
+ *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
+ *                     \/            \/     \/    \/            \/
+ * $Id$
+ *
+ * Copyright (C) 2014 Franklin Wei
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ***************************************************************************/
+
+#include "video.h"
+#include "stdint.h"
+#define FONT_SIZE (8 * (0x80 - ' '))
+#define MAX_STRING_TABLE_SIZE 255
+
+extern uint8_t video_font[FONT_SIZE];
+
+extern struct StrEntry video_stringsTableEng[MAX_STRING_TABLE_SIZE];
diff --git a/apps/plugins/xworld/vm.c b/apps/plugins/xworld/vm.c
new file mode 100644
index 0000000..de632d7
--- /dev/null
+++ b/apps/plugins/xworld/vm.c
@@ -0,0 +1,763 @@
+/***************************************************************************
+ *             __________               __   ___.
+ *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
+ *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
+ *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
+ *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
+ *                     \/            \/     \/    \/            \/
+ * $Id$
+ *
+ * Copyright (C) 2014 Franklin Wei, Benjamin Brown
+ * Copyright (C) 2004 Gregory Montoir
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ***************************************************************************/
+
+#include "plugin.h"
+#include "vm.h"
+#include "mixer.h"
+#include "resource.h"
+#include "video.h"
+#include "serializer.h"
+#include "sfxplayer.h"
+#include "sys.h"
+#include "parts.h"
+#include "file.h"
+
+static const uint16_t vm_frequenceTable[] = {
+    0x0CFF, 0x0DC3, 0x0E91, 0x0F6F, 0x1056, 0x114E, 0x1259, 0x136C,
+    0x149F, 0x15D9, 0x1726, 0x1888, 0x19FD, 0x1B86, 0x1D21, 0x1EDE,
+    0x20AB, 0x229C, 0x24B3, 0x26D7, 0x293F, 0x2BB2, 0x2E4C, 0x3110,
+    0x33FB, 0x370D, 0x3A43, 0x3DDF, 0x4157, 0x4538, 0x4998, 0x4DAE,
+    0x5240, 0x5764, 0x5C9A, 0x61C8, 0x6793, 0x6E19, 0x7485, 0x7BBD
+};
+
+void vm_create(struct VirtualMachine* m, struct Mixer *mix,  struct Resource* res, struct SfxPlayer *ply, struct Video *vid, struct System *stub)
+{
+    m->res = res;
+    m->video = vid;
+    m->sys = stub;
+    m->mixer = mix;
+    m->player = ply;
+}
+
+void vm_init(struct VirtualMachine* m) {
+
+    rb->memset(m->vmVariables, 0, sizeof(m->vmVariables));
+    m->vmVariables[0x54] = 0x81;
+    m->vmVariables[VM_VARIABLE_RANDOM_SEED] = *rb->current_tick;
+
+    m->_fastMode = false;
+    m->player->_markVar = &m->vmVariables[VM_VARIABLE_MUS_MARK];
+}
+
+void vm_op_movConst(struct VirtualMachine* m) {
+    uint8_t variableId = scriptPtr_fetchByte(&m->_scriptPtr);
+    int16_t value = scriptPtr_fetchWord(&m->_scriptPtr);
+    debug(DBG_VM, "vm_op_movConst(0x%02X, %d)", variableId, value);
+    m->vmVariables[variableId] = value;
+}
+
+void vm_op_mov(struct VirtualMachine* m) {
+    uint8_t dstVariableId = scriptPtr_fetchByte(&m->_scriptPtr);
+    uint8_t srcVariableId = scriptPtr_fetchByte(&m->_scriptPtr);
+    debug(DBG_VM, "vm_op_mov(0x%02X, 0x%02X)", dstVariableId, srcVariableId);
+    m->vmVariables[dstVariableId] = m->vmVariables[srcVariableId];
+}
+
+void vm_op_add(struct VirtualMachine* m) {
+    uint8_t dstVariableId = scriptPtr_fetchByte(&m->_scriptPtr);
+    uint8_t srcVariableId = scriptPtr_fetchByte(&m->_scriptPtr);
+    debug(DBG_VM, "vm_op_add(0x%02X, 0x%02X)", dstVariableId, srcVariableId);
+    m->vmVariables[dstVariableId] += m->vmVariables[srcVariableId];
+}
+
+void vm_op_addConst(struct VirtualMachine* m) {
+    if (m->res->currentPartId == 0x3E86 && m->_scriptPtr.pc == m->res->segBytecode + 0x6D48) {
+        warning("vm_op_addConst() hack for non-stop looping gun sound bug");
+        // the script 0x27 slot 0x17 doesn't stop the gun sound from looping, I
+        // don't really know why ; for now, let's play the 'stopping sound' like
+        // the other scripts do
+        //  (0x6D43) jmp(0x6CE5)
+        //  (0x6D46) break
+        //  (0x6D47) VAR(6) += -50
+        vm_snd_playSound(m, 0x5B, 1, 64, 1);
+    }
+    uint8_t variableId = scriptPtr_fetchByte(&m->_scriptPtr);
+    int16_t value = scriptPtr_fetchWord(&m->_scriptPtr);
+    debug(DBG_VM, "vm_op_addConst(0x%02X, %d)", variableId, value);
+    m->vmVariables[variableId] += value;
+}
+
+void vm_op_call(struct VirtualMachine* m) {
+
+    uint16_t offset = scriptPtr_fetchWord(&m->_scriptPtr);
+    uint8_t sp = m->_stackPtr;
+
+    debug(DBG_VM, "vm_op_call(0x%X)", offset);
+    m->_scriptStackCalls[sp] = m->_scriptPtr.pc - m->res->segBytecode;
+    if (m->_stackPtr == 0xFF) {
+        error("vm_op_call() ec=0x%X stack overflow", 0x8F);
+    }
+    ++m->_stackPtr;
+    m->_scriptPtr.pc = m->res->segBytecode + offset ;
+}
+
+void vm_op_ret(struct VirtualMachine* m) {
+    debug(DBG_VM, "vm_op_ret()");
+    if (m->_stackPtr == 0) {
+        error("vm_op_ret() ec=0x%X stack underflow", 0x8F);
+    }
+    --m->_stackPtr;
+    uint8_t sp = m->_stackPtr;
+    m->_scriptPtr.pc = m->res->segBytecode + m->_scriptStackCalls[sp];
+}
+
+void vm_op_pauseThread(struct VirtualMachine* m) {
+    debug(DBG_VM, "vm_op_pauseThread()");
+    m->gotoNextThread = true;
+}
+
+void vm_op_jmp(struct VirtualMachine* m) {
+    uint16_t pcOffset = scriptPtr_fetchWord(&m->_scriptPtr);
+    debug(DBG_VM, "vm_op_jmp(0x%02X)", pcOffset);
+    m->_scriptPtr.pc = m->res->segBytecode + pcOffset;
+}
+
+void vm_op_setSetVect(struct VirtualMachine* m) {
+    uint8_t threadId = scriptPtr_fetchByte(&m->_scriptPtr);
+    uint16_t pcOffsetRequested = scriptPtr_fetchWord(&m->_scriptPtr);
+    debug(DBG_VM, "vm_op_setSetVect(0x%X, 0x%X)", threadId, pcOffsetRequested);
+    m->threadsData[REQUESTED_PC_OFFSET][threadId] = pcOffsetRequested;
+}
+
+void vm_op_jnz(struct VirtualMachine* m) {
+    uint8_t i = scriptPtr_fetchByte(&m->_scriptPtr);
+    debug(DBG_VM, "vm_op_jnz(0x%02X)", i);
+    --m->vmVariables[i];
+    if (m->vmVariables[i] != 0) {
+        vm_op_jmp(m);
+    } else {
+        scriptPtr_fetchWord(&m->_scriptPtr);
+    }
+}
+
+#define BYPASS_PROTECTION
+void vm_op_condJmp(struct VirtualMachine* m) {
+
+    //printf("Jump : %X \n",m->_scriptPtr.pc-m->res->segBytecode);
+//FCS Whoever wrote this is patching the bytecode on the fly. This is ballzy !!
+#ifdef BYPASS_PROTECTION
+
+    if (m->res->currentPartId == GAME_PART_FIRST && m->_scriptPtr.pc == m->res->segBytecode + 0xCB9) {
+
+        // (0x0CB8) condJmp(0x80, VAR(41), VAR(30), 0xCD3)
+        *(m->_scriptPtr.pc + 0x00) = 0x81;
+        *(m->_scriptPtr.pc + 0x03) = 0x0D;
+        *(m->_scriptPtr.pc + 0x04) = 0x24;
+        // (0x0D4E) condJmp(0x4, VAR(50), 6, 0xDBC)
+        *(m->_scriptPtr.pc + 0x99) = 0x0D;
+        *(m->_scriptPtr.pc + 0x9A) = 0x5A;
+        debug(DBG_VM, "vm_op_condJmp() bypassing protection");
+        debug(DBG_VM, "bytecode has been patched");
+
+        //vm_bypassProtection(m);
+    }
+
+
+#endif
+
+    uint8_t opcode = scriptPtr_fetchByte(&m->_scriptPtr);
+    int16_t b = m->vmVariables[scriptPtr_fetchByte(&m->_scriptPtr)];
+    uint8_t c = scriptPtr_fetchByte(&m->_scriptPtr);
+    int16_t a;
+
+    if (opcode & 0x80) {
+        a = m->vmVariables[c];
+    } else if (opcode & 0x40) {
+        a = c * 256 + scriptPtr_fetchByte(&m->_scriptPtr);
+    } else {
+        a = c;
+    }
+    debug(DBG_VM, "vm_op_condJmp(%d, 0x%02X, 0x%02X)", opcode, b, a);
+
+    // Check if the conditional value is met.
+    bool expr = false;
+    switch (opcode & 7) {
+    case 0:     // jz
+        expr = (b == a);
+        break;
+    case 1: // jnz
+        expr = (b != a);
+        break;
+    case 2: // jg
+        expr = (b > a);
+        break;
+    case 3: // jge
+        expr = (b >= a);
+        break;
+    case 4: // jl
+        expr = (b < a);
+        break;
+    case 5: // jle
+        expr = (b <= a);
+        break;
+    default:
+        warning("vm_op_condJmp() invalid condition %d", (opcode & 7));
+        break;
+    }
+
+    if (expr) {
+        vm_op_jmp(m);
+    } else {
+        scriptPtr_fetchWord(&m->_scriptPtr);
+    }
+
+}
+
+void vm_op_setPalette(struct VirtualMachine* m) {
+    uint16_t paletteId = scriptPtr_fetchWord(&m->_scriptPtr);
+    debug(DBG_VM, "vm_op_changePalette(%d)", paletteId);
+    m->video->paletteIdRequested = paletteId >> 8;
+}
+
+void vm_op_resetThread(struct VirtualMachine* m) {
+
+    uint8_t threadId = scriptPtr_fetchByte(&m->_scriptPtr);
+    uint8_t i =        scriptPtr_fetchByte(&m->_scriptPtr);
+
+    // FCS: WTF, this is cryptic as hell !!
+    // int8_t n = (i & 0x3F) - threadId;  //0x3F = 0011 1111
+    // The following is so much clearer
+
+    //Make sure i is within [0-VM_NUM_THREADS-1]
+    i = i & (VM_NUM_THREADS - 1) ;
+    int8_t n = i - threadId;
+
+    if (n < 0) {
+        warning("vm_op_m->resetThread() ec=0x%X (n < 0)", 0x880);
+        return;
+    }
+    ++n;
+    uint8_t a = scriptPtr_fetchByte(&m->_scriptPtr);
+
+    debug(DBG_VM, "vm_op_m->resetThread(%d, %d, %d)", threadId, i, a);
+
+    if (a == 2) {
+        uint16_t *p = &m->threadsData[REQUESTED_PC_OFFSET][threadId];
+        while (n--) {
+            *p++ = 0xFFFE;
+        }
+    } else if (a < 2) {
+        uint8_t *p = &m->vmIsChannelActive[REQUESTED_STATE][threadId];
+        while (n--) {
+            *p++ = a;
+        }
+    }
+}
+
+void vm_op_selectVideoPage(struct VirtualMachine* m) {
+    uint8_t frameBufferId = scriptPtr_fetchByte(&m->_scriptPtr);
+    debug(DBG_VM, "vm_op_selectVideoPage(%d)", frameBufferId);
+    video_changePagePtr1(m->video, frameBufferId);
+}
+
+void vm_op_fillVideoPage(struct VirtualMachine* m) {
+    uint8_t pageId = scriptPtr_fetchByte(&m->_scriptPtr);
+    uint8_t color = scriptPtr_fetchByte(&m->_scriptPtr);
+    debug(DBG_VM, "vm_op_fillVideoPage(%d, %d)", pageId, color);
+    video_fillPage(m->video, pageId, color);
+}
+
+void vm_op_copyVideoPage(struct VirtualMachine* m) {
+    uint8_t srcPageId = scriptPtr_fetchByte(&m->_scriptPtr);
+    uint8_t dstPageId = scriptPtr_fetchByte(&m->_scriptPtr);
+    debug(DBG_VM, "vm_op_copyVideoPage(%d, %d)", srcPageId, dstPageId);
+    video_copyPage(m->video, srcPageId, dstPageId, m->vmVariables[VM_VARIABLE_SCROLL_Y]);
+}
+
+
+static uint32_t lastTimeStamp = 0;
+void vm_op_blitFramebuffer(struct VirtualMachine* m) {
+
+    uint8_t pageId = scriptPtr_fetchByte(&m->_scriptPtr);
+    debug(DBG_VM, "vm_op_blitFramebuffer(%d)", pageId);
+    vm_inp_handleSpecialKeys(m);
+
+    /* Nasty hack....was this present in the original assembly  ??!! */
+    if (m->res->currentPartId == GAME_PART_FIRST && m->vmVariables[0x67] == 1)
+        m->vmVariables[0xDC] = 0x21;
+
+    if (!m->_fastMode) {
+
+        int32_t delay = sys_getTimeStamp(m->sys) - lastTimeStamp;
+        int32_t timeToSleep = m->vmVariables[VM_VARIABLE_PAUSE_SLICES] * 20 - delay;
+
+        /* The bytecode will set m->vmVariables[VM_VARIABLE_PAUSE_SLICES] from 1 to 5 */
+        /* The virtual machine hence indicates how long the image should be displayed. */
+
+        if (timeToSleep > 0)
+        {
+            sys_sleep(m->sys, timeToSleep);
+        }
+
+        lastTimeStamp = sys_getTimeStamp(m->sys);
+    }
+
+    /* WTF ? */
+    m->vmVariables[0xF7] = 0;
+
+    video_updateDisplay(m->video, pageId);
+}
+
+void vm_op_killThread(struct VirtualMachine* m) {
+    debug(DBG_VM, "vm_op_killThread()");
+    m->_scriptPtr.pc = m->res->segBytecode + 0xFFFF;
+    m->gotoNextThread = true;
+}
+
+void vm_op_drawString(struct VirtualMachine* m) {
+    uint16_t stringId = scriptPtr_fetchWord(&m->_scriptPtr);
+    uint16_t x = scriptPtr_fetchByte(&m->_scriptPtr);
+    uint16_t y = scriptPtr_fetchByte(&m->_scriptPtr);
+    uint16_t color = scriptPtr_fetchByte(&m->_scriptPtr);
+
+    debug(DBG_VM, "vm_op_drawString(0x%03X, %d, %d, %d)", stringId, x, y, color);
+
+    video_drawString(m->video, color, x, y, stringId);
+}
+
+void vm_op_sub(struct VirtualMachine* m) {
+    uint8_t i = scriptPtr_fetchByte(&m->_scriptPtr);
+    uint8_t j = scriptPtr_fetchByte(&m->_scriptPtr);
+    debug(DBG_VM, "vm_op_sub(0x%02X, 0x%02X)", i, j);
+    m->vmVariables[i] -= m->vmVariables[j];
+}
+
+void vm_op_and(struct VirtualMachine* m) {
+    uint8_t variableId = scriptPtr_fetchByte(&m->_scriptPtr);
+    uint16_t n = scriptPtr_fetchWord(&m->_scriptPtr);
+    debug(DBG_VM, "vm_op_and(0x%02X, %d)", variableId, n);
+    m->vmVariables[variableId] = (uint16_t)m->vmVariables[variableId] & n;
+}
+
+void vm_op_or(struct VirtualMachine* m) {
+    uint8_t variableId = scriptPtr_fetchByte(&m->_scriptPtr);
+    uint16_t value = scriptPtr_fetchWord(&m->_scriptPtr);
+    debug(DBG_VM, "vm_op_or(0x%02X, %d)", variableId, value);
+    m->vmVariables[variableId] = (uint16_t)m->vmVariables[variableId] | value;
+}
+
+void vm_op_shl(struct VirtualMachine* m) {
+    uint8_t variableId = scriptPtr_fetchByte(&m->_scriptPtr);
+    uint16_t leftShiftValue = scriptPtr_fetchWord(&m->_scriptPtr);
+    debug(DBG_VM, "vm_op_shl(0x%02X, %d)", variableId, leftShiftValue);
+    m->vmVariables[variableId] = (uint16_t)m->vmVariables[variableId] << leftShiftValue;
+}
+
+void vm_op_shr(struct VirtualMachine* m) {
+    uint8_t variableId = scriptPtr_fetchByte(&m->_scriptPtr);
+    uint16_t rightShiftValue = scriptPtr_fetchWord(&m->_scriptPtr);
+    debug(DBG_VM, "vm_op_shr(0x%02X, %d)", variableId, rightShiftValue);
+    m->vmVariables[variableId] = (uint16_t)m->vmVariables[variableId] >> rightShiftValue;
+}
+
+void vm_op_playSound(struct VirtualMachine* m) {
+    uint16_t resourceId = scriptPtr_fetchWord(&m->_scriptPtr);
+    uint8_t freq = scriptPtr_fetchByte(&m->_scriptPtr);
+    uint8_t vol = scriptPtr_fetchByte(&m->_scriptPtr);
+    uint8_t channel = scriptPtr_fetchByte(&m->_scriptPtr);
+    debug(DBG_VM, "vm_op_playSound(0x%X, %d, %d, %d)", resourceId, freq, vol, channel);
+    vm_snd_playSound(m, resourceId, freq, vol, channel);
+}
+
+void vm_op_updateMemList(struct VirtualMachine* m) {
+
+    uint16_t resourceId = scriptPtr_fetchWord(&m->_scriptPtr);
+    debug(DBG_VM, "vm_op_updateMemList(%d)", resourceId);
+
+    if (resourceId == 0) {
+        player_stop(m->player);
+        mixer_stopAll(m->mixer);
+        res_invalidateRes(m->res);
+    } else {
+        res_loadPartsOrMemoryEntry(m->res, resourceId);
+    }
+}
+
+void vm_op_playMusic(struct VirtualMachine* m) {
+    uint16_t resNum = scriptPtr_fetchWord(&m->_scriptPtr);
+    uint16_t delay = scriptPtr_fetchWord(&m->_scriptPtr);
+    uint8_t pos = scriptPtr_fetchByte(&m->_scriptPtr);
+    debug(DBG_VM, "vm_op_playMusic(0x%X, %d, %d)", resNum, delay, pos);
+    vm_snd_playMusic(m, resNum, delay, pos);
+}
+
+void vm_initForPart(struct VirtualMachine* m, uint16_t partId) {
+
+    player_stop(m->player);
+    mixer_stopAll(m->mixer);
+
+    /* WTF is that ? */
+    m->vmVariables[0xE4] = 0x14;
+
+    res_setupPart(m->res, partId);
+
+    /* Set all thread to inactive (pc at 0xFFFF or 0xFFFE ) */
+    rb->memset((uint8_t *)m->threadsData, 0xFF, sizeof(m->threadsData));
+
+    rb->memset((uint8_t *)m->vmIsChannelActive, 0, sizeof(m->vmIsChannelActive));
+
+    int firstThreadId = 0;
+    m->threadsData[PC_OFFSET][firstThreadId] = 0;
+}
+
+/*
+  This is called every frames in the infinite loop.
+*/
+void vm_checkThreadRequests(struct VirtualMachine* m) {
+
+    /* Check if a part switch has been requested. */
+    if (m->res->requestedNextPart != 0) {
+        vm_initForPart(m, m->res->requestedNextPart);
+        m->res->requestedNextPart = 0;
+    }
+
+
+    /* Check if a state update has been requested for any thread during the previous VM execution: */
+    /*      - Pause */
+    /*      - Jump */
+
+    /* JUMP: */
+    /* Note: If a jump has been requested, the jump destination is stored */
+    /* in m->threadsData[REQUESTED_PC_OFFSET]. Otherwise m->threadsData[REQUESTED_PC_OFFSET] == 0xFFFF */
+
+    /* PAUSE: */
+    /* Note: If a pause has been requested it is stored in  m->vmIsChannelActive[REQUESTED_STATE][i] */
+
+    for (int threadId = 0; threadId < VM_NUM_THREADS; threadId++) {
+
+        m->vmIsChannelActive[CURR_STATE][threadId] = m->vmIsChannelActive[REQUESTED_STATE][threadId];
+
+        uint16_t n = m->threadsData[REQUESTED_PC_OFFSET][threadId];
+
+        if (n != VM_NO_SETVEC_REQUESTED) {
+
+            m->threadsData[PC_OFFSET][threadId] = (n == 0xFFFE) ? VM_INACTIVE_THREAD : n;
+            m->threadsData[REQUESTED_PC_OFFSET][threadId] = VM_NO_SETVEC_REQUESTED;
+        }
+    }
+}
+
+void vm_hostFrame(struct VirtualMachine* m) {
+
+    /* Run the Virtual Machine for every active threads (one vm frame). */
+    /* Inactive threads are marked with a thread instruction pointer set to 0xFFFF (VM_INACTIVE_THREAD). */
+    /* A thread must feature a break opcode so the interpreter can move to the next thread. */
+
+    for (int threadId = 0; threadId < VM_NUM_THREADS; threadId++) {
+
+        if (m->vmIsChannelActive[CURR_STATE][threadId])
+            continue;
+
+        uint16_t n = m->threadsData[PC_OFFSET][threadId];
+
+        if (n != VM_INACTIVE_THREAD) {
+
+            /* Set the script pointer to the right location. */
+            /* script pc is used in executeThread in order */
+            /* to get the next opcode. */
+            m->_scriptPtr.pc = m->res->segBytecode + n;
+            m->_stackPtr = 0;
+
+            m->gotoNextThread = false;
+            debug(DBG_VM, "vm_hostFrame() i=0x%02X n=0x%02X *p=0x%02X", threadId, n, *m->_scriptPtr.pc);
+            vm_executeThread(m);
+
+            /* Since .pc is going to be modified by this next loop iteration, we need to save it. */
+            m->threadsData[PC_OFFSET][threadId] = m->_scriptPtr.pc - m->res->segBytecode;
+
+
+            debug(DBG_VM, "vm_hostFrame() i=0x%02X pos=0x%X", threadId, m->threadsData[PC_OFFSET][threadId]);
+            if (m->sys->input.quit) {
+                break;
+            }
+        }
+    }
+}
+
+#define COLOR_BLACK 0xFF
+#define DEFAULT_ZOOM 0x40
+
+
+void vm_executeThread(struct VirtualMachine* m) {
+
+    while (!m->gotoNextThread) {
+        uint8_t opcode = scriptPtr_fetchByte(&m->_scriptPtr);
+
+        /* 1000 0000 is set */
+        if (opcode & 0x80)
+        {
+            uint16_t off = ((opcode << 8) | scriptPtr_fetchByte(&m->_scriptPtr)) * 2;
+            m->res->_useSegVideo2 = false;
+            int16_t x = scriptPtr_fetchByte(&m->_scriptPtr);
+            int16_t y = scriptPtr_fetchByte(&m->_scriptPtr);
+            int16_t h = y - 199;
+            if (h > 0) {
+                y = 199;
+                x += h;
+            }
+            debug(DBG_VIDEO, "vid_opcd_0x80 : opcode=0x%X off=0x%X x=%d y=%d", opcode, off, x, y);
+
+            /* This switch the polygon database to "cinematic" and probably draws a black polygon */
+            /* over all the screen. */
+            video_setDataBuffer(m->video, m->res->segCinematic, off);
+            struct Point temp;
+            temp.x = x;
+            temp.y = y;
+            video_readAndDrawPolygon(m->video, COLOR_BLACK, DEFAULT_ZOOM, &temp);
+
+            continue;
+        }
+
+        /* 0100 0000 is set */
+        if (opcode & 0x40)
+        {
+            int16_t x, y;
+            uint16_t off = scriptPtr_fetchWord(&m->_scriptPtr) * 2;
+            x = scriptPtr_fetchByte(&m->_scriptPtr);
+
+            m->res->_useSegVideo2 = false;
+
+            if (!(opcode & 0x20))
+            {
+                if (!(opcode & 0x10))  /* 0001 0000 is set */
+                {
+                    x = (x << 8) | scriptPtr_fetchByte(&m->_scriptPtr);
+                } else {
+                    x = m->vmVariables[x];
+                }
+            }
+            else
+            {
+                if (opcode & 0x10) { /* 0001 0000 is set */
+                    x += 0x100;
+                }
+            }
+
+            y = scriptPtr_fetchByte(&m->_scriptPtr);
+
+            if (!(opcode & 8))  /* 0000 1000 is set */
+            {
+                if (!(opcode & 4)) { /* 0000 0100 is set */
+                    y = (y << 8) | scriptPtr_fetchByte(&m->_scriptPtr);
+                } else {
+                    y = m->vmVariables[y];
+                }
+            }
+
+            uint16_t zoom = scriptPtr_fetchByte(&m->_scriptPtr);
+
+            if (!(opcode & 2))  /* 0000 0010 is set */
+            {
+                if (!(opcode & 1)) /* 0000 0001 is set */
+                {
+                    --m->_scriptPtr.pc;
+                    zoom = 0x40;
+                }
+                else
+                {
+                    zoom = m->vmVariables[zoom];
+                }
+            }
+            else
+            {
+
+                if (opcode & 1) { /* 0000 0001 is set */
+                    m->res->_useSegVideo2 = true;
+                    --m->_scriptPtr.pc;
+                    zoom = 0x40;
+                }
+            }
+            debug(DBG_VIDEO, "vid_opcd_0x40 : off=0x%X x=%d y=%d", off, x, y);
+            video_setDataBuffer(m->video, m->res->_useSegVideo2 ? m->res->_segVideo2 : m->res->segCinematic, off);
+            struct Point temp;
+            temp.x = x;
+            temp.y = y;
+            video_readAndDrawPolygon(m->video, 0xFF, zoom, &temp);
+
+            continue;
+        }
+
+
+        if (opcode > 0x1A)
+        {
+            error("vm_executeThread() ec=0x%X invalid opcode=0x%X", 0xFFF, opcode);
+        }
+        else
+        {
+            (vm_opcodeTable[opcode])(m);
+        }
+
+        rb->yield();
+    }
+}
+
+void vm_inp_updatePlayer(struct VirtualMachine* m) {
+
+    sys_processEvents(m->sys);
+
+    if (m->res->currentPartId == 0x3E89) {
+        char c = m->sys->input.lastChar;
+        if (c == 8 || /*c == 0xD |*/ c == 0 || (c >= 'a' && c <= 'z')) {
+            m->vmVariables[VM_VARIABLE_LAST_KEYCHAR] = c & ~0x20;
+            m->sys->input.lastChar = 0;
+        }
+    }
+
+    int16_t lr   = 0;
+    int16_t mask = 0;
+    int16_t ud   = 0;
+
+    if (m->sys->input.dirMask & DIR_RIGHT) {
+        lr = 1;
+        mask |= 1;
+    }
+    if (m->sys->input.dirMask & DIR_LEFT) {
+        lr = -1;
+        mask |= 2;
+    }
+    if (m->sys->input.dirMask & DIR_DOWN) {
+        ud = 1;
+        mask |= 4;
+    }
+
+    m->vmVariables[VM_VARIABLE_HERO_POS_UP_DOWN] = ud;
+
+    if (m->sys->input.dirMask & DIR_UP) {
+        m->vmVariables[VM_VARIABLE_HERO_POS_UP_DOWN] = -1;
+    }
+
+    if (m->sys->input.dirMask & DIR_UP) { /* inpJump */
+        ud = -1;
+        mask |= 8;
+    }
+
+    m->vmVariables[VM_VARIABLE_HERO_POS_JUMP_DOWN] = ud;
+    m->vmVariables[VM_VARIABLE_HERO_POS_LEFT_RIGHT] = lr;
+    m->vmVariables[VM_VARIABLE_HERO_POS_MASK] = mask;
+    int16_t button = 0;
+
+    if (m->sys->input.button) {
+        button = 1;
+        mask |= 0x80;
+    }
+
+    m->vmVariables[VM_VARIABLE_HERO_ACTION] = button;
+    m->vmVariables[VM_VARIABLE_HERO_ACTION_POS_MASK] = mask;
+}
+
+void vm_inp_handleSpecialKeys(struct VirtualMachine* m) {
+
+    if (m->sys->input.pause) {
+
+        if (m->res->currentPartId != GAME_PART1 && m->res->currentPartId != GAME_PART2) {
+            m->sys->input.pause = false;
+            while (!m->sys->input.pause) {
+                sys_processEvents(m->sys);
+                sys_sleep(m->sys, 200);
+            }
+        }
+        m->sys->input.pause = false;
+    }
+
+    if (m->sys->input.code) {
+        m->sys->input.code = false;
+        if (m->res->currentPartId != GAME_PART_LAST && m->res->currentPartId != GAME_PART_FIRST) {
+            m->res->requestedNextPart = GAME_PART_LAST;
+        }
+    }
+
+    /* User has inputted a bad code, the "ERROR" screen is showing */
+    if (m->vmVariables[0xC9] == 1) {
+        debug(DBG_VM, "vm_inp_handleSpecialKeys() unhandled case (m->vmVariables[0xC9] == 1)");
+    }
+
+}
+
+void vm_snd_playSound(struct VirtualMachine* m, uint16_t resNum, uint8_t freq, uint8_t vol, uint8_t channel) {
+
+    debug(DBG_SND, "snd_playSound(0x%X, %d, %d, %d)", resNum, freq, vol, channel);
+
+    struct MemEntry *me = &m->res->_memList[resNum];
+
+    if (me->state != MEMENTRY_STATE_LOADED)
+        return;
+
+
+    if (vol == 0) {
+        mixer_stopChannel(m->mixer, channel);
+    } else {
+        struct MixerChunk mc;
+        rb->memset(&mc, 0, sizeof(mc));
+        mc.data = me->bufPtr + 8; /* skip header */
+        mc.len = READ_BE_UINT16(me->bufPtr) * 2;
+        mc.loopLen = READ_BE_UINT16(me->bufPtr + 2) * 2;
+        if (mc.loopLen != 0) {
+            mc.loopPos = mc.len;
+        }
+        assert(freq < 40);
+        mixer_playChannel(m->mixer, channel & 3, &mc, vm_frequenceTable[freq], MIN(vol, 0x3F));
+    }
+
+}
+
+void vm_snd_playMusic(struct VirtualMachine* m, uint16_t resNum, uint16_t delay, uint8_t pos) {
+
+    debug(DBG_SND, "snd_playMusic(0x%X, %d, %d)", resNum, delay, pos);
+
+    if (resNum != 0) {
+        player_loadSfxModule(m->player, resNum, delay, pos);
+        player_start(m->player);
+    } else if (delay != 0) {
+        player_setEventsDelay(m->player, delay);
+    } else {
+        player_stop(m->player);
+    }
+}
+
+void vm_saveOrLoad(struct VirtualMachine* m, struct Serializer *ser) {
+    struct Entry entries[] = {
+        SE_ARRAY(m->vmVariables, 0x100, SES_INT16, VER(1)),
+        SE_ARRAY(m->_scriptStackCalls, 0x100, SES_INT16, VER(1)),
+        SE_ARRAY(m->threadsData, 0x40 * 2, SES_INT16, VER(1)),
+        SE_ARRAY(m->vmIsChannelActive, 0x40 * 2, SES_INT8, VER(1)),
+        SE_END()
+    };
+    ser_saveOrLoadEntries(ser, entries);
+}
+
+void vm_bypassProtection(struct VirtualMachine* m)
+{
+    File f;
+    file_create(&f, true);
+    if (!file_open(&f, "bank0e", res_getDataDir(m->res), "rb")) {
+        warning("Unable to bypass protection: add bank0e file to datadir");
+    } else {
+        struct Serializer s;
+        ser_create(&s, &f, SM_LOAD, m->res->_memPtrStart, 2);
+        vm_saveOrLoad(m, &s);
+        res_saveOrLoad(m->res, &s);
+        video_saveOrLoad(m->video, &s);
+        player_saveOrLoad(m->player, &s);
+        mixer_saveOrLoad(m->mixer, &s);
+    }
+    file_close(&f);
+}
diff --git a/apps/plugins/xworld/vm.h b/apps/plugins/xworld/vm.h
new file mode 100644
index 0000000..1409dd4
--- /dev/null
+++ b/apps/plugins/xworld/vm.h
@@ -0,0 +1,184 @@
+/***************************************************************************
+ *             __________               __   ___.
+ *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
+ *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
+ *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
+ *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
+ *                     \/            \/     \/    \/            \/
+ * $Id$
+ *
+ * Copyright (C) 2014 Franklin Wei, Benjamin Brown
+ * Copyright (C) 2004 Gregory Montoir
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ***************************************************************************/
+
+#ifndef __LOGIC_H__
+#define __LOGIC_H__
+
+
+
+#include "intern.h"
+
+#define VM_NUM_THREADS 64
+#define VM_NUM_VARIABLES 256
+#define VM_NO_SETVEC_REQUESTED 0xFFFF
+#define VM_INACTIVE_THREAD    0xFFFF
+
+
+#define    VM_VARIABLE_RANDOM_SEED          0x3C
+
+#define    VM_VARIABLE_LAST_KEYCHAR         0xDA
+
+#define    VM_VARIABLE_HERO_POS_UP_DOWN     0xE5
+#define    VM_VARIABLE_MUS_MARK             0xF4
+
+#define    VM_VARIABLE_SCROLL_Y             0xF9
+#define    VM_VARIABLE_HERO_ACTION          0xFA
+#define    VM_VARIABLE_HERO_POS_JUMP_DOWN   0xFB
+#define    VM_VARIABLE_HERO_POS_LEFT_RIGHT  0xFC
+#define    VM_VARIABLE_HERO_POS_MASK        0xFD
+#define    VM_VARIABLE_HERO_ACTION_POS_MASK 0xFE
+#define    VM_VARIABLE_PAUSE_SLICES         0xFF
+
+struct Mixer;
+struct Resource;
+struct Serializer;
+struct SfxPlayer;
+struct System;
+struct Video;
+
+//For threadsData navigation
+#define PC_OFFSET 0
+#define REQUESTED_PC_OFFSET 1
+#define NUM_DATA_FIELDS 2
+
+//For vmIsChannelActive navigation
+#define CURR_STATE 0
+#define REQUESTED_STATE 1
+#define NUM_THREAD_FIELDS 2
+
+struct VirtualMachine;
+
+void vm_create(struct VirtualMachine*, struct Mixer *mix, struct Resource *res, struct SfxPlayer *ply, struct Video *vid, struct System *stub);
+void vm_init(struct VirtualMachine*);
+
+void vm_op_movConst(struct VirtualMachine*);
+void vm_op_mov(struct VirtualMachine*);
+void vm_op_add(struct VirtualMachine*);
+void vm_op_addConst(struct VirtualMachine*);
+void vm_op_call(struct VirtualMachine*);
+void vm_op_ret(struct VirtualMachine*);
+void vm_op_pauseThread(struct VirtualMachine*);
+void vm_op_jmp(struct VirtualMachine*);
+void vm_op_setSetVect(struct VirtualMachine*);
+void vm_op_jnz(struct VirtualMachine*);
+void vm_op_condJmp(struct VirtualMachine*);
+void vm_op_setPalette(struct VirtualMachine*);
+void vm_op_resetThread(struct VirtualMachine*);
+void vm_op_selectVideoPage(struct VirtualMachine*);
+void vm_op_fillVideoPage(struct VirtualMachine*);
+void vm_op_copyVideoPage(struct VirtualMachine*);
+void vm_op_blitFramebuffer(struct VirtualMachine*);
+void vm_op_killThread(struct VirtualMachine*);
+void vm_op_drawString(struct VirtualMachine*);
+void vm_op_sub(struct VirtualMachine*);
+void vm_op_and(struct VirtualMachine*);
+void vm_op_or(struct VirtualMachine*);
+void vm_op_shl(struct VirtualMachine*);
+void vm_op_shr(struct VirtualMachine*);
+void vm_op_playSound(struct VirtualMachine*);
+void vm_op_updateMemList(struct VirtualMachine*);
+void vm_op_playMusic(struct VirtualMachine*);
+
+void vm_initForPart(struct VirtualMachine*, uint16_t partId);
+void vm_setupPart(struct VirtualMachine*, uint16_t partId);
+void vm_checkThreadRequests(struct VirtualMachine*);
+void vm_hostFrame(struct VirtualMachine*);
+void vm_executeThread(struct VirtualMachine*);
+
+void vm_inp_updatePlayer(struct VirtualMachine*);
+void vm_inp_handleSpecialKeys(struct VirtualMachine*);
+
+void vm_snd_playSound(struct VirtualMachine*, uint16_t resNum, uint8_t freq, uint8_t vol, uint8_t channel);
+void vm_snd_playMusic(struct VirtualMachine*, uint16_t resNum, uint16_t delay, uint8_t pos);
+
+void vm_saveOrLoad(struct VirtualMachine*, struct Serializer *ser);
+void vm_bypassProtection(struct VirtualMachine*);
+
+typedef void (*OpcodeStub)(struct VirtualMachine*);
+
+// The type of entries in opcodeTable. This allows "fast" branching
+static const OpcodeStub vm_opcodeTable[] = {
+    /* 0x00 */
+    &vm_op_movConst,
+    &vm_op_mov,
+    &vm_op_add,
+    &vm_op_addConst,
+    /* 0x04 */
+    &vm_op_call,
+    &vm_op_ret,
+    &vm_op_pauseThread,
+    &vm_op_jmp,
+    /* 0x08 */
+    &vm_op_setSetVect,
+    &vm_op_jnz,
+    &vm_op_condJmp,
+    &vm_op_setPalette,
+    /* 0x0C */
+    &vm_op_resetThread,
+    &vm_op_selectVideoPage,
+    &vm_op_fillVideoPage,
+    &vm_op_copyVideoPage,
+    /* 0x10 */
+    &vm_op_blitFramebuffer,
+    &vm_op_killThread,
+    &vm_op_drawString,
+    &vm_op_sub,
+    /* 0x14 */
+    &vm_op_and,
+    &vm_op_or,
+    &vm_op_shl,
+    &vm_op_shr,
+    /* 0x18 */
+    &vm_op_playSound,
+    &vm_op_updateMemList,
+    &vm_op_playMusic
+};
+
+struct VirtualMachine {
+    //This table is used to play a sound
+    //static const uint16_t frequenceTable[];
+    /* FW: moved from staticres.c to vm.c */
+
+    struct Mixer *mixer;
+    struct Resource *res;
+    struct SfxPlayer *player;
+    struct Video *video;
+    struct System *sys;
+
+    int16_t vmVariables[VM_NUM_VARIABLES];
+    uint16_t _scriptStackCalls[VM_NUM_THREADS];
+
+    uint16_t threadsData[NUM_DATA_FIELDS][VM_NUM_THREADS];
+    // This array is used:
+    //     0 to save the channel's instruction pointer
+    //     when the channel release control (this happens on a break).
+
+    //     1 When a setVec is requested for the next vm frame.
+
+    uint8_t vmIsChannelActive[NUM_THREAD_FIELDS][VM_NUM_THREADS];
+
+    struct Ptr _scriptPtr;
+    uint8_t _stackPtr;
+    bool gotoNextThread;
+    bool _fastMode;
+};
+#endif
diff --git a/apps/plugins/xworld/xworld.c b/apps/plugins/xworld/xworld.c
new file mode 100644
index 0000000..932ff1c
--- /dev/null
+++ b/apps/plugins/xworld/xworld.c
@@ -0,0 +1,244 @@
+/***************************************************************************
+ *             __________               __   ___.
+ *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
+ *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
+ *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
+ *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
+ *                     \/            \/     \/    \/            \/
+ * $Id$
+ *
+ * Copyright (C) 2014 Franklin Wei, Benjamin Brown
+ * Copyright (C) 2004 Gregory Montoir
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ***************************************************************************/
+
+#include "plugin.h"
+#include "engine.h"
+#include "sys.h"
+#include "util.h"
+
+/* we don't want these on the stack, they're big and could cause a stack overflow */
+static struct Engine e;
+static struct System sys;
+
+enum plugin_status plugin_start(const void* parameter)
+{
+    (void) parameter;
+
+    /* no trailing slashes */
+    const char *dataPath = "/.rockbox/xworld";
+    const char *savePath = "/.rockbox/xworld";
+    g_debugMask = 0;
+
+    engine_create(&e, &sys, dataPath, savePath);
+    engine_init(&e);
+    sys_menu(&sys);
+
+    engine_run(&e);
+
+    engine_finish(&e);
+    return PLUGIN_OK;
+}
+
+
+
+/*
+   Game was originally made with 16. SIXTEEN colors. Running on 320x200 (64,000 pixels.)
+
+   Great fan site here: https://sites.google.com/site/interlinkknight/anotherworld
+   Contains the wheelcode :P !
+
+   A lot of details can be found regarding the game and engine architecture at:
+   http://www.anotherworld.fr/anotherworld_uk/another_world.htm
+
+   The chronology of the game implementation can retraced via the ordering of the opcodes:
+   The sound and music opcode are at the end: Music and sound was done at the end.
+
+   Call tree:
+   =========
+
+   SDLSystem       systemImplementaion ;
+   System *sys = & systemImplementaion ;
+
+   main
+   {
+
+       Engine *e = new Engine();
+           e->run()
+           {
+              sys->init("Out Of This World");
+              setup();
+              vm.restartAt(0x3E80); // demo starts at 0x3E81
+
+             while (!_stub->_pi.quit)
+                 {
+                   vm.setupScripts();
+                   vm.inp_updatePlayer();
+                    processInput();
+                   vm.runScripts();
+             }
+
+             finish();
+           }
+   }
+
+
+   Virtual Machine:
+   ================
+
+           Seems the threading model is collaborative multi-tasking (as opposed to preemptive multitasking):
+           A thread (called a Channel on Eric Chahi's website) will release the hand to the next one via the
+           break opcode.
+
+           It seems that even when a setvec is requested by a thread, we cannot set the instruction pointer
+           yet. The thread is allowed to keep on executing its code for the remaining of the vm frame.
+
+           A virtual machine frame has a variable duration. The unit of time is 20ms and the frame can be set
+           to live for 1 (20ms ; 50Hz) up to 5 (100ms ; 10Hz).
+
+
+           There are 30 something opcodes. The graphic opcode are more complex, not only the declare the operation to perform
+           they also define where to find the vertices (segVideo1 or segVideo2).
+
+           No stack available but a thread can save its pc (Program Counter) once: One method call and return is possible.
+
+
+   Video :
+   =======
+           Double buffer architecture. AW opcodes even has a special instruction for blitting from one
+           frame buffer to an other.
+
+           Double buffering is implemented in software
+
+           According to Eric Chahi's webpage there are 4 framebuffer. Since on full screenbuffer is 320x200/2 = 32kB
+           that would mean the total size consumed is 128KB ?
+
+   Sound :
+   =======
+           Mixing is done on software.
+
+           Since the virtual machine and SDL are running simultaneously in two different threads:
+           Any read or write to an elements of the sound channels MUST be synchronized with a
+           mutex.
+
+   FastMode :
+   ==========
+
+   The game engine features a "fast-mode"...what it to be able to respond to the now defunct
+   TURBO button commonly found on 386/486 era PC ?!
+
+
+   Endianess:
+   ==========
+
+   Atari and Amiga used bigEndian CPUs. Data are hence stored within BANK in big endian format.
+   On an Intel or ARM CPU data will have to be transformed when read.
+
+
+
+   The original codebase contained a looooot of cryptic hexa values.
+   0x100 (for 256 variables)
+   0x400 (for one kilobyte)
+   0x40 (for num threads)
+   0x3F (num thread mask)
+   I cleaned that up.
+
+   Questions & Answers :
+   =====================
+
+   Q: How does the interpreter deals with the CPU speed ?! A pentium is a tad faster than a Motorola 68000
+      after all.
+   A: See vm frame time: The vm frame duration is variable. The vm actually write for how long a video frame
+      should be displayed in variable 0xFF. The value is the number of 20ms slice
+
+   Q: Why is a palette 2048 bytes if there are only 16 colors ? I would have expected 48 bytes...
+   A: ???
+
+   Q: Why does Resource::load() search for ressource to load from higher to lower....since it will load stuff
+      until no more ressources are marked as "Need to be loaded".
+   A: ???
+
+   Original DOS version :
+   ======================
+
+   Banks: 1,236,519 B
+   exe  :    20,293 B
+
+
+   Total bank      size: 1236519 (100%)
+   ---------------------------------
+   Total RT_SOUND    size: 585052  ( 47%)
+   Total RT_MUSIC    size:   3540  (  0%)
+   Total RT_POLY_ANIM   size: 106676  (  9%)
+   Total RT_PALETTE      size:  11032  (  1%)
+   Total RT_BYTECODE size: 135948  ( 11%)
+   Total RT_POLY_CINEMATIC     size: 291008  ( 24%)
+
+   As usual sounds are the most consuming assets (Quake1,Quake2 etc.....)
+
+
+   memlist.bin features 146 entries :
+   ==================================
+
+   Most important part in an entry are:
+
+   bankId          : - Give the file were the resource is.
+   offset          : - How much to skip in the file before hiting the resource.
+   size,packetSize : - How much to read, should we unpack what we read.
+
+
+
+   Polygons drawing :
+   =================
+
+   Polygons can be given as:
+    - a pure screenspace sequence of points: I call those screenspace polygons.
+        - a list of delta to add or substract to the first vertex. I call those: objectspace polygons.
+
+   Video :
+   =======
+
+   Q: Why 4 framebuffer ?
+   A: It seems the background is generated once (like in the introduction) and stored in a framebuffer.
+      Every frame the saved background is copied and new elements are drawn on top.
+
+
+   Trivia :
+   ========
+
+   If you are used to RGBA 32bits per pixel framebuffer you are in for a shock:
+   Another world is 16 colors palette based, making it 4bits per pixel !!
+
+   Video generation :
+   ==================
+
+   Thank god the engine sets the palette before starting to drawing instead of after bliting.
+   I would have been unable to generate screenshots otherwise.
+
+   Memory managment :
+   =================
+
+   There is 0 malloc during the game. All resources are loaded in one big buffer (Resource::load).
+   The entire buffer is "freed" at the end of a game part.
+
+
+   The renderer is actually capable of Blending a new poly in the framebuffer (Video::drawLineT)
+
+
+        I am almost sure that:
+        _curPagePtr1 is the backbuffer
+        _curPagePtr2 is the frontbuffer
+        _curPagePtr3 is the background builder.
+
+
+   * Why does memlist.bin uses a special state field 0xFF in order to mark the end of resources ??!
+     It would have been so much easier to write the number of resources at the beginning of the code.
+*/
diff --git a/apps/plugins/xworld/xworld.make b/apps/plugins/xworld/xworld.make
new file mode 100644
index 0000000..7d69668
--- /dev/null
+++ b/apps/plugins/xworld/xworld.make
@@ -0,0 +1,27 @@
+#             __________               __   ___.
+#   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
+#   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
+#   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
+#   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
+#                     \/            \/     \/    \/            \/
+# $Id$
+#
+
+XWORLDSRCDIR := $(APPSDIR)/plugins/xworld
+XWORLDBUILDDIR := $(BUILDDIR)/apps/plugins/xworld
+
+ROCKS += $(XWORLDBUILDDIR)/xworld.rock
+
+XWORLD_SRC := $(call preprocess, $(XWORLDSRCDIR)/SOURCES)
+XWORLD_OBJ := $(call c2obj, $(XWORLD_SRC))
+
+# add source files to OTHER_SRC to get automatic dependencies
+OTHER_SRC += $(XWORLD_SRC)
+
+XWORLDFLAGS = $(filter-out -O%,$(PLUGINFLAGS)) -O2
+
+$(XWORLDBUILDDIR)/xworld.rock: $(XWORLD_OBJ)
+
+$(XWORLDBUILDDIR)/%.o: $(XWORLDSRCDIR)/%.c $(XWORLDSRCDIR)/xworld.make
+	$(SILENT)mkdir -p $(dir $@)
+	$(call PRINTS,CC $(subst $(ROOTDIR)/,,$<))$(CC) -I$(dir $<) $(XWORLDFLAGS) -c $< -o $@
diff --git a/docs/CREDITS b/docs/CREDITS
index 96a5b78..0f548e2 100644
--- a/docs/CREDITS
+++ b/docs/CREDITS
@@ -672,3 +672,5 @@
 The MikMod team
 Michael McTernan (The ARM unwinder author)
 Albert Song
+The New RAW team (Piotr Padkowski and others)
+The Fabother World team (Fabien Sanglard and others)
diff --git a/manual/plugins/main.tex b/manual/plugins/main.tex
index a49cfac..4143fd6 100644
--- a/manual/plugins/main.tex
+++ b/manual/plugins/main.tex
@@ -94,6 +94,8 @@
 
 \opt{lcd_bitmap}{\input{plugins/xobox.tex}}
 
+\opt{lcd_bitmap}{\input{plugins/xworld.tex}}
+
 \section{Demos}
 
 \opt{lcd_bitmap}{\input{plugins/bounce.tex}}
diff --git a/manual/plugins/xworld.tex b/manual/plugins/xworld.tex
new file mode 100644
index 0000000..32e3ecb
--- /dev/null
+++ b/manual/plugins/xworld.tex
@@ -0,0 +1,81 @@
+\subsection{XWorld}
+
+In this cinematic, award winning platform game by Éric Chahi, you must evade capture
+and do your best to escape an alien planet.  After an experiment goes awry the hero
+must team up with an unlikely ally, when they both become fugitives on another world.
+XWorld requires the data files, bank* and memlist.bin, from the original "Another World"
+PC game to be copied into the .rockbox/xworld/ directory before the game can be played.
+
+\begin{btnmap}
+    %
+    \opt{RECORDER_PAD,ONDIO_PAD,IRIVER_H100_PAD,IRIVER_H300_PAD,IAUDIO_X5_PAD%
+        ,SANSA_E200_PAD,SANSA_FUZE_PAD,SANSA_C200_PAD,SANSA_CLIP_PAD,GIGABEAT_PAD%
+        ,GIGABEAT_S_PAD,MROBE100_PAD,PBELL_VIBE500_PAD,SANSA_FUZEPLUS_PAD%
+        ,SAMSUNG_YH92X_PAD,SAMSUNG_YH820_PAD}
+        {\ButtonUp}
+    \opt{IPOD_4G_PAD,IPOD_3G_PAD,IPOD_1G2G_PAD}{\ButtonMenu}
+    \opt{IRIVER_H10_PAD}{\ButtonScrollUp}
+    \opt{HAVE_TOUCHSCREEN}{\TouchTopMiddle}
+    \opt{PBELL_VIBE500_PAD}{\ButtonOk}
+       \opt{HAVEREMOTEKEYMAP}{& }
+    & Up and Jump \\
+    %
+    \opt{RECORDER_PAD,ONDIO_PAD,IRIVER_H100_PAD,IRIVER_H300_PAD,IAUDIO_X5_PAD%
+        ,SANSA_E200_PAD,SANSA_FUZE_PAD,SANSA_C200_PAD,SANSA_CLIP_PAD,GIGABEAT_PAD%
+        ,GIGABEAT_S_PAD,MROBE100_PAD,PBELL_VIBE500_PAD,SANSA_FUZEPLUS_PAD%
+        ,SAMSUNG_YH92X_PAD,SAMSUNG_YH820_PAD}
+        {\ButtonDown}
+    \opt{IPOD_4G_PAD,IPOD_3G_PAD,IPOD_1G2G_PAD}{\ButtonPlay}
+    \opt{IRIVER_H10_PAD}{\ButtonScrollDown}
+    \opt{HAVE_TOUCHSCREEN}{\TouchBottomMiddle}
+    \opt{PBELL_VIBE500_PAD}{\ButtonCancel}
+       \opt{HAVEREMOTEKEYMAP}{& }
+    & Down and Crouch\\
+    %
+    \opt{RECORDER_PAD,ONDIO_PAD,IRIVER_H100_PAD,IRIVER_H300_PAD,IAUDIO_X5_PAD%
+        ,SANSA_E200_PAD,SANSA_FUZE_PAD,SANSA_C200_PAD,SANSA_CLIP_PAD,GIGABEAT_PAD%
+        ,GIGABEAT_S_PAD,MROBE100_PAD,PBELL_VIBE500_PAD,SANSA_FUZEPLUS_PAD%
+        ,SAMSUNG_YH92X_PAD,SAMSUNG_YH820_PAD,IPOD_4G_PAD,IPOD_3G_PAD,IPOD_1G2G_PAD%
+        ,IRIVER_H10_PAD}
+        {\ButtonLeft / \ButtonRight}
+    \opt{HAVE_TOUCHSCREEN}{\TouchMidLeft / \TouchMidRight}
+    \opt{PBELL_VIBE500_PAD}{\ButtonMenu / \ButtonPlay}
+       \opt{HAVEREMOTEKEYMAP}{& }
+    & Move Left and Right\\
+    %
+    \opt{SANSA_FUZE_PAD}{\ButtonHome}
+    \opt{SAMSUNG_YH920_PAD}{\ButtonFFWD}
+    \opt{IRIVER_H300_PAD,SANSA_E200_PAD,SAMSUNG_YH820_PAD,IAUDIO_X5M5_PAD}{\ButtonRec}
+    \opt{IPOD_4G_PAD,IPOD_3G_PAD,IPOD_1G2G_PAD,CREATIVE_ZEN_PAD,SANSA_CLIP_PAD}{\ButtonSelect}
+    \opt{SONY_NWZ_PAD,CREATIVEZVM_PAD}{\ButtonPlay}
+    \opt{ONDAVX777_PAD,MROBE500_PAD,PBELL_VIBE500_PAD}{\ButtonPower}
+    \opt{SAMSUNG_YPR0_PAD}{\ButtonUser}
+    \opt{IRIVER_H10_PAD}{\ButtonRew}
+    \opt{HM801_PAD}{\ButtonPrev}
+    \opt{SONY_NWZ_PAD,CREATIVEZVM_PAD}{\ButtonPlay}
+    \opt{MROBE500_PAD}{\ButtonPower}
+    \opt{DX50_PAD,ONDAVX747_PAD,PHILIPS_HDD1630_PAD,PHILIPS_HDD6330_PAD,PHILIPS_SA9200_PAD%
+        ,CREATIVE_ZENXFI2_PAD,CREATIVE_ZENXFI3_PAD,SANSA_CONNECT_PAD,SANSA_C200_PAD%
+        ,SANSA_FUZEPLUS_PAD,GIGABEAT_PAD,GIGABEAT_S_PAD}{\ButtonVolUp}
+    \opt{HAVE_TOUCHSCREEN}{\ButtonBottomLeft}
+       \opt{HAVEREMOTEKEYMAP}{& }
+    & Action and Fire\\
+    %
+    \opt{DX50_PAD,ONDAVX747_PAD,PHILIPS_HDD1630_PAD,PHILIPS_HDD6330_PAD,PHILIPS_SA9200_PAD%
+        ,CREATIVE_ZENXFI2_PAD,CREATIVE_ZENXFI3_PAD,SANSA_CONNECT_PAD,SANSA_C200_PAD%
+        ,SANSA_FUZEPLUS_PAD}{\ButtonVolDown}
+    \opt{GIGABEAT_PAD,GIGABEAT_S_PAD}{\ButtonMenu}
+    \opt{SANSA_FUZE_PAD}{\ButtonSelect}
+    \opt{SAMSUNG_YH920_PAD}{\ButtonRew}
+    \opt{SAMSUNG_YH820_PAD,IAUDIO_X5M5_PAD}{\ButtonPlay}
+    \opt{SANSA_E200_PAD,SANSA_CLIP_PAD}{\ButtonPower}
+    \opt{CREATIVE_ZEN_PAD,SONY_NWZ_PAD}{\ButtonBack}
+    \opt{CREATIVEZVM_PAD,SAMSUNG_YPR0_PAD}{\ButtonMenu}
+    \opt{IRIVER_H300_PAD}{\ButtonMode}
+    \opt{HM801_PAD}{\ButtonNext}
+    \opt{PBELL_VIBE500_PAD}{\ButtonRec}
+    \opt{IRIVER_H10_PAD}{\ButtonPlay}
+    \opt{IPOD_4G_PAD,IPOD_3G_PAD,IPOD_1G2G_PAD}{\ButtonMenu / \ButtonSelect}
+       \opt{HAVEREMOTEKEYMAP}{& }
+    & Menu\\
+\end{btnmap}