From 33cb13dee5a527ac445ea1b13d42723e4eb3e3b0 Mon Sep 17 00:00:00 2001 From: Franklin Wei Date: Mon, 13 Oct 2014 21:00:47 -0400 Subject: 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 --- apps/plugins/CATEGORIES | 1 + apps/plugins/SUBDIRS | 6 + apps/plugins/xworld/README | 86 +++ apps/plugins/xworld/README.newraw | 4 + apps/plugins/xworld/README.rockbox | 51 ++ apps/plugins/xworld/SOURCES | 15 + apps/plugins/xworld/awendian.h | 51 ++ apps/plugins/xworld/bank.c | 153 +++++ apps/plugins/xworld/bank.h | 55 ++ apps/plugins/xworld/engine.c | 397 +++++++++++++ apps/plugins/xworld/engine.h | 70 +++ apps/plugins/xworld/file.c | 172 ++++++ apps/plugins/xworld/file.h | 51 ++ apps/plugins/xworld/intern.c | 34 ++ apps/plugins/xworld/intern.h | 44 ++ apps/plugins/xworld/keymaps.h | 183 ++++++ apps/plugins/xworld/mixer.c | 199 +++++++ apps/plugins/xworld/mixer.h | 69 +++ apps/plugins/xworld/parts.c | 56 ++ apps/plugins/xworld/parts.h | 57 ++ apps/plugins/xworld/resource.c | 443 ++++++++++++++ apps/plugins/xworld/resource.h | 107 ++++ apps/plugins/xworld/serializer.c | 141 +++++ apps/plugins/xworld/serializer.h | 84 +++ apps/plugins/xworld/sfxplayer.c | 247 ++++++++ apps/plugins/xworld/sfxplayer.h | 88 +++ apps/plugins/xworld/sys.c | 942 +++++++++++++++++++++++++++++ apps/plugins/xworld/sys.h | 146 +++++ apps/plugins/xworld/util.c | 82 +++ apps/plugins/xworld/util.h | 59 ++ apps/plugins/xworld/video.c | 1141 ++++++++++++++++++++++++++++++++++++ apps/plugins/xworld/video.h | 127 ++++ apps/plugins/xworld/video_data.c | 271 +++++++++ apps/plugins/xworld/video_data.h | 29 + apps/plugins/xworld/vm.c | 763 ++++++++++++++++++++++++ apps/plugins/xworld/vm.h | 184 ++++++ apps/plugins/xworld/xworld.c | 244 ++++++++ apps/plugins/xworld/xworld.make | 27 + docs/CREDITS | 2 + manual/plugins/main.tex | 2 + manual/plugins/xworld.tex | 81 +++ 41 files changed, 6964 insertions(+) create mode 100644 apps/plugins/xworld/README create mode 100644 apps/plugins/xworld/README.newraw create mode 100644 apps/plugins/xworld/README.rockbox create mode 100644 apps/plugins/xworld/SOURCES create mode 100644 apps/plugins/xworld/awendian.h create mode 100644 apps/plugins/xworld/bank.c create mode 100644 apps/plugins/xworld/bank.h create mode 100644 apps/plugins/xworld/engine.c create mode 100644 apps/plugins/xworld/engine.h create mode 100644 apps/plugins/xworld/file.c create mode 100644 apps/plugins/xworld/file.h create mode 100644 apps/plugins/xworld/intern.c create mode 100644 apps/plugins/xworld/intern.h create mode 100644 apps/plugins/xworld/keymaps.h create mode 100644 apps/plugins/xworld/mixer.c create mode 100644 apps/plugins/xworld/mixer.h create mode 100644 apps/plugins/xworld/parts.c create mode 100644 apps/plugins/xworld/parts.h create mode 100644 apps/plugins/xworld/resource.c create mode 100644 apps/plugins/xworld/resource.h create mode 100644 apps/plugins/xworld/serializer.c create mode 100644 apps/plugins/xworld/serializer.h create mode 100644 apps/plugins/xworld/sfxplayer.c create mode 100644 apps/plugins/xworld/sfxplayer.h create mode 100644 apps/plugins/xworld/sys.c create mode 100644 apps/plugins/xworld/sys.h create mode 100644 apps/plugins/xworld/util.c create mode 100644 apps/plugins/xworld/util.h create mode 100644 apps/plugins/xworld/video.c create mode 100644 apps/plugins/xworld/video.h create mode 100644 apps/plugins/xworld/video_data.c create mode 100644 apps/plugins/xworld/video_data.h create mode 100644 apps/plugins/xworld/vm.c create mode 100644 apps/plugins/xworld/vm.h create mode 100644 apps/plugins/xworld/xworld.c create mode 100644 apps/plugins/xworld/xworld.make create mode 100644 manual/plugins/xworld.tex diff --git a/apps/plugins/CATEGORIES b/apps/plugins/CATEGORIES index 609f359e7d..fd7a49af8f 100644 --- a/apps/plugins/CATEGORIES +++ b/apps/plugins/CATEGORIES @@ -134,4 +134,5 @@ wavrecord,apps wavview,viewers wormlet,games xobox,games +xworld,games zxbox,viewers diff --git a/apps/plugins/SUBDIRS b/apps/plugins/SUBDIRS index 8e653983b7..4b4015be08 100644 --- a/apps/plugins/SUBDIRS +++ b/apps/plugins/SUBDIRS @@ -12,6 +12,12 @@ clock /* 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 0000000000..3a07b1ba94 --- /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 0000000000..f5163c978c --- /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 0000000000..2ea63faa17 --- /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!!! ********** +************************************** +************************************** + + +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 0000000000..98b5f14fb8 --- /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 0000000000..863b19bb47 --- /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 0000000000..e2cfbd4680 --- /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 0000000000..6f280c5271 --- /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 0000000000..0d1c1bfa61 --- /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_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 0000000000..a0ad2b469e --- /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 0000000000..8353544ef5 --- /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 0000000000..b24cdaf19e --- /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 0000000000..0d58100f37 --- /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 0000000000..3355f28eb1 --- /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 0000000000..edba05b9d2 --- /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 0000000000..de7536cd36 --- /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 0000000000..9447bcfb81 --- /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 0000000000..12b0b3b7b6 --- /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 0000000000..684306cc6d --- /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 0000000000..2820dcb998 --- /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 0000000000..3242777bc2 --- /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 0000000000..5268c2784c --- /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 0000000000..fde8f6d076 --- /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 0000000000..9bdc143d02 --- /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 0000000000..0bf92b93d0 --- /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 0000000000..4bcdfafebd --- /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 0000000000..f1920acf37 --- /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 0000000000..b82691ad29 --- /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 +#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 0000000000..8852335661 --- /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 0000000000..b4182df7a7 --- /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 0000000000..d4ff64f5b5 --- /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 0000000000..e658c175d9 --- /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 0000000000..cd53205744 --- /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 0000000000..de632d710d --- /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 0000000000..1409dd47e3 --- /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 0000000000..932ff1c3e1 --- /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 0000000000..7d6966842b --- /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 96a5b784bd..0f548e2810 100644 --- a/docs/CREDITS +++ b/docs/CREDITS @@ -672,3 +672,5 @@ The Pure Data team (Miller Puckette and others) 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 a49cfacbef..4143fd6b63 100644 --- a/manual/plugins/main.tex +++ b/manual/plugins/main.tex @@ -94,6 +94,8 @@ text files% \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 0000000000..32e3ecbf78 --- /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} -- cgit v1.2.3