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}