summaryrefslogtreecommitdiff
path: root/apps/plugins/xworld/engine.c
diff options
context:
space:
mode:
authorFranklin Wei <frankhwei536@gmail.com>2014-10-13 21:00:47 -0400
committerMichael Giacomelli <giac2000@hotmail.com>2014-12-23 23:48:12 +0100
commit33cb13dee5a527ac445ea1b13d42723e4eb3e3b0 (patch)
tree3ce36ea21b53377b900049143e77e74b77ca1b0d /apps/plugins/xworld/engine.c
parentb681e932a9da797249ddc0e4ccab7ed7cf50fd41 (diff)
downloadrockbox-33cb13dee5a527ac445ea1b13d42723e4eb3e3b0.zip
rockbox-33cb13dee5a527ac445ea1b13d42723e4eb3e3b0.tar.gz
rockbox-33cb13dee5a527ac445ea1b13d42723e4eb3e3b0.tar.bz2
rockbox-33cb13dee5a527ac445ea1b13d42723e4eb3e3b0.tar.xz
Xworld - Another World interpreter for Rockbox
Co-conspirators: Franklin Wei, Benjamin Brown -------------------------------------------------------------------- This work is based on: - Fabien Sanglard's "Fabother World" based on - Piotr Padkowski's newRaw interpreter which was based on - Gregory Montoir's reverse engineering of - Eric Chahi's assembly code -------------------------------------------------------------------- Progress: * The plugin runs pretty nicely (with sound!) on most color targets * Keymaps for color LCD targets are complete * The manual entry is finished * Grayscale/monochrome support is NOT PLANNED - the game looks horrible in grayscale! :p -------------------------------------------------------------------- Notes: * The original game strings were built-in to the executable, and were copyrighted and could not be used. * This port ships with an alternate set of strings by default, but can load the "official" strings from a file at runtime. -------------------------------------------------------------------- To be done (in descending order of importance): * vertical stride compatibility <30% done> * optimization <10% done> Change-Id: I3155b0d97c2ac470cb8a2040f40d4139ddcebfa5 Reviewed-on: http://gerrit.rockbox.org/1077 Reviewed-by: Michael Giacomelli <giac2000@hotmail.com>
Diffstat (limited to 'apps/plugins/xworld/engine.c')
-rw-r--r--apps/plugins/xworld/engine.c397
1 files changed, 397 insertions, 0 deletions
diff --git a/apps/plugins/xworld/engine.c b/apps/plugins/xworld/engine.c
new file mode 100644
index 0000000..0d1c1bf
--- /dev/null
+++ b/apps/plugins/xworld/engine.c
@@ -0,0 +1,397 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2014 Franklin Wei, Benjamin Brown
+ * Copyright (C) 2004 Gregory Montoir
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ***************************************************************************/
+
+#include "plugin.h"
+#include "engine.h"
+#include "file.h"
+#include "serializer.h"
+#include "sys.h"
+#include "parts.h"
+#include "video_data.h"
+#include "video.h"
+
+void engine_create(struct Engine* e, struct System* stub, const char* dataDir, const char* saveDir)
+{
+ e->sys = stub;
+ e->sys->e = e;
+ e->_dataDir = dataDir;
+ e->_saveDir = saveDir;
+
+ mixer_create(&e->mixer, e->sys);
+
+ /* this needs to be here and not engine_init() to ensure that it is not called on a reset */
+ res_create(&e->res, &e->video, e->sys, dataDir);
+
+ res_allocMemBlock(&e->res);
+
+ video_create(&e->video, &e->res, e->sys);
+
+ player_create(&e->player, &e->mixer, &e->res, e->sys);
+
+ vm_create(&e->vm, &e->mixer, &e->res, &e->player, &e->video, e->sys);
+}
+
+void engine_run(struct Engine* e) {
+
+ while (!e->sys->input.quit) {
+
+ vm_checkThreadRequests(&e->vm);
+
+ vm_inp_updatePlayer(&e->vm);
+
+ engine_processInput(e);
+
+ vm_hostFrame(&e->vm);
+ }
+
+}
+
+/*
+ * this function loads the font in XWORLD_FONT_FILE into video_font
+ */
+
+/*
+ * the file format for the font file is like this:
+ * "XFNT" magic
+ * 8-bit version number
+ * <768 bytes data>
+ * sum of data, XOR'ed by version number repeated 4 times (32-bit)
+ */
+bool engine_loadFontFile(struct Engine* e)
+{
+ uint8_t *old_font = sys_get_buffer(e->sys, sizeof(video_font));
+ rb->memcpy(old_font, video_font, sizeof(video_font));
+
+ File f;
+ file_create(&f, false);
+ if(!file_open(&f, XWORLD_FONT_FILE, e->_dataDir, "rb"))
+ {
+ goto fail;
+ }
+
+ /* read header */
+ char header[5];
+ int ret = file_read(&f, header, sizeof(header));
+ if(ret != sizeof(header) ||
+ header[0] != 'X' ||
+ header[1] != 'F' ||
+ header[2] != 'N' ||
+ header[3] != 'T')
+ {
+ warning("Invalid font file signature, falling back to alternate font");
+ goto fail;
+ }
+
+ if(header[4] != XWORLD_FONT_VERSION)
+ {
+ warning("Font file version mismatch (have=%d, need=%d), falling back to alternate font", header[4], XWORLD_FONT_VERSION);
+ goto fail;
+ }
+
+ uint32_t sum = 0;
+ for(unsigned int i = 0;i<sizeof(video_font);++i)
+ {
+ sum += video_font[i] = file_readByte(&f);
+ }
+
+ uint32_t mask = (header[4] << 24) |
+ (header[4] << 16) |
+ (header[4] << 8 ) |
+ (header[4] << 0 );
+ sum ^= mask;
+ uint32_t check = file_readUint32BE(&f);
+
+ if(check != sum)
+ {
+ warning("Bad font checksum, falling back to alternate font");
+ goto fail;
+ }
+
+ file_close(&f);
+ return true;
+
+fail:
+ file_close(&f);
+
+ memcpy(video_font, old_font, sizeof(video_font));
+ return false;
+}
+
+/*
+ * this function loads the string table in STRING_TABLE_FILE into
+ * video_stringsTableEng
+ */
+
+/*
+ * the file format for the string table is like this:
+ * "XWST" magic
+ * 8-bit version number
+ * 8-bit title length
+ * <title data (0-255 bytes, _NO NULL_)
+ * 16-bit number of string entries (currently limited to 255)
+ * entry format:
+ struct file_entry_t
+ {
+ uint16_t id;
+ uint16_t len; - length of str
+ char* str; - NO NULL
+ }
+*/
+bool engine_loadStringTable(struct Engine* e)
+{
+ File f;
+ file_create(&f, false);
+ if(!file_open(&f, STRING_TABLE_FILE, e->_dataDir, "rb"))
+ {
+ /*
+ * this gives verbose warnings while loadFontFile doesn't because the font looks similar
+ * enough to pass for the "original", but the strings don't
+ */
+ warning("Unable to find string table, falling back to alternate strings");
+ goto fail;
+ }
+
+ /* read header */
+
+ char header[5];
+ int ret = file_read(&f, header, sizeof(header));
+ if(ret != sizeof(header) ||
+ header[0] != 'X' ||
+ header[1] != 'W' ||
+ header[2] != 'S' ||
+ header[3] != 'T')
+ {
+ warning("Invalid string table signature, falling back to alternate strings");
+ goto fail;
+ }
+
+ if(header[4] != STRING_TABLE_VERSION)
+ {
+ warning("String table version mismatch (have=%d, need=%d), falling back to alternate strings", header[4], STRING_TABLE_VERSION);
+ goto fail;
+ }
+
+ /* read title */
+
+ uint8_t title_length = file_readByte(&f);
+ char *title_buf;
+ if(title_length)
+ {
+ title_buf = sys_get_buffer(e->sys, (int32_t)title_length + 1); /* make room for the NULL */
+ ret = file_read(&f, title_buf, title_length);
+ if(ret != title_length)
+ {
+ warning("Title shorter than expected, falling back to alternate strings");
+ goto fail;
+ }
+ }
+ else
+ {
+ title_buf = "UNKNOWN";
+ }
+
+ /* read entries */
+
+ uint16_t num_entries = file_readUint16BE(&f);
+ for(unsigned int i = 0; i < num_entries && i < ARRAYLEN(video_stringsTableEng); ++i)
+ {
+ video_stringsTableEng[i].id = file_readUint16BE(&f);
+ uint16_t len = file_readUint16BE(&f);
+
+ if(file_ioErr(&f))
+ {
+ warning("Unexpected EOF in while parsing entry %d, falling back to alternate strings", i);
+ goto fail;
+ }
+
+ video_stringsTableEng[i].str = sys_get_buffer(e->sys, (int32_t)len + 1);
+
+ ret = file_read(&f, video_stringsTableEng[i].str, len);
+ if(ret != len)
+ {
+ warning("Entry %d too short, falling back to alternate strings", i);
+ goto fail;
+ }
+ }
+
+ file_close(&f);
+ rb->splashf(HZ, "String table '%s' loaded", title_buf);
+ return true;
+fail:
+ file_close(&f);
+ return false;
+}
+
+void engine_init(struct Engine* e) {
+ sys_init(e->sys, "Out Of This World");
+
+ res_readEntries(&e->res);
+
+ engine_loadStringTable(e);
+
+ engine_loadFontFile(e);
+
+ video_init(&e->video);
+
+ vm_init(&e->vm);
+
+ mixer_init(&e->mixer);
+
+ player_init(&e->player);
+
+ /* Init virtual machine, legacy way */
+ /* vm_initForPart(&e->vm, GAME_PART_FIRST); // This game part is the protection screen */
+
+ /* Try to cheat here. You can jump anywhere but the VM crashes afterward. */
+ /* Starting somewhere is probably not enough, the variables and calls return are probably missing. */
+ /* vm_initForPart(&e->vm, GAME_PART2); Skip protection screen and go directly to intro */
+ /* vm_initForPart(&e->vm, GAME_PART3); CRASH */
+ /* vm_initForPart(&e->vm, GAME_PART4); Start directly in jail but then crash */
+ /* vm->initForPart(&e->vm, GAME_PART5); CRASH */
+ /* vm->initForPart(GAME_PART6); Start in the battlechar but CRASH afteward */
+ /* vm->initForPart(GAME_PART7); CRASH */
+ /* vm->initForPart(GAME_PART8); CRASH */
+ /* vm->initForPart(GAME_PART9); Green screen not doing anything */
+}
+
+void engine_finish(struct Engine* e) {
+ player_free(&e->player);
+ mixer_free(&e->mixer);
+ res_freeMemBlock(&e->res);
+}
+
+void engine_processInput(struct Engine* e) {
+ if (e->sys->input.load) {
+ engine_loadGameState(e, e->_stateSlot);
+ e->sys->input.load = false;
+ }
+ if (e->sys->input.save) {
+ engine_saveGameState(e, e->_stateSlot, "quicksave");
+ e->sys->input.save = false;
+ }
+ if (e->sys->input.fastMode) {
+ e->vm._fastMode = !&e->vm._fastMode;
+ e->sys->input.fastMode = false;
+ }
+ if (e->sys->input.stateSlot != 0) {
+ int8_t slot = e->_stateSlot + e->sys->input.stateSlot;
+ if (slot >= 0 && slot < MAX_SAVE_SLOTS) {
+ e->_stateSlot = slot;
+ debug(DBG_INFO, "Current game state slot is %d", e->_stateSlot);
+ }
+ e->sys->input.stateSlot = 0;
+ }
+}
+
+void engine_makeGameStateName(struct Engine* e, uint8_t slot, char *buf, int sz) {
+ (void) e;
+ rb->snprintf(buf, sz, "xworld_save.s%02d", slot);
+}
+
+void engine_saveGameState(struct Engine* e, uint8_t slot, const char *desc) {
+ char stateFile[20];
+ /* sizeof(char) is guaranteed to be 1 */
+ engine_makeGameStateName(e, slot, stateFile, sizeof(stateFile));
+ File f;
+ file_create(&f, false);
+ if (!file_open(&f, stateFile, e->_saveDir, "wb")) {
+ warning("Unable to save state file '%s'", stateFile);
+ } else {
+ /* header */
+ file_writeUint32BE(&f, SAVE_MAGIC);
+ file_writeUint16BE(&f, CUR_VER);
+ file_writeUint16BE(&f, 0);
+ char hdrdesc[32];
+ strncpy(hdrdesc, desc, sizeof(hdrdesc) - 1);
+ file_write(&f, hdrdesc, sizeof(hdrdesc));
+ /* contents */
+ struct Serializer s;
+ ser_create(&s, &f, SM_SAVE, e->res._memPtrStart, CUR_VER);
+ vm_saveOrLoad(&e->vm, &s);
+ res_saveOrLoad(&e->res, &s);
+ video_saveOrLoad(&e->video, &s);
+ player_saveOrLoad(&e->player, &s);
+ mixer_saveOrLoad(&e->mixer, &s);
+ if (file_ioErr(&f)) {
+ warning("I/O error when saving game state");
+ } else {
+ debug(DBG_INFO, "Saved state to slot %d", e->_stateSlot);
+ }
+ }
+ file_close(&f);
+}
+
+bool engine_loadGameState(struct Engine* e, uint8_t slot) {
+ char stateFile[20];
+ engine_makeGameStateName(e, slot, stateFile, 20);
+ File f;
+ file_create(&f, false);
+ if (!file_open(&f, stateFile, e->_saveDir, "rb")) {
+ debug(DBG_ENG, "Unable to open state file '%s'", stateFile);
+ goto fail;
+ } else {
+ uint32_t id = file_readUint32BE(&f);
+ if (id != SAVE_MAGIC) {
+ debug(DBG_ENG, "Bad savegame format");
+ goto fail;
+ } else {
+ /* mute */
+ player_stop(&e->player);
+ mixer_stopAll(&e->mixer);
+ /* header */
+ uint16_t ver = file_readUint16BE(&f);
+ file_readUint16BE(&f);
+ char hdrdesc[32];
+ file_read(&f, hdrdesc, sizeof(hdrdesc));
+ /* contents */
+ struct Serializer s;
+ ser_create(&s, &f, SM_LOAD, e->res._memPtrStart, ver);
+ vm_saveOrLoad(&e->vm, &s);
+ res_saveOrLoad(&e->res, &s);
+ video_saveOrLoad(&e->video, &s);
+ player_saveOrLoad(&e->player, &s);
+ mixer_saveOrLoad(&e->mixer, &s);
+ }
+ if (file_ioErr(&f)) {
+ debug(DBG_ENG, "I/O error when loading game state");
+ goto fail;
+ } else {
+ debug(DBG_INFO, "Loaded state from slot %d", e->_stateSlot);
+ }
+ }
+ file_close(&f);
+ return true;
+fail:
+ file_close(&f);
+ return false;
+}
+
+void engine_deleteGameState(struct Engine* e, uint8_t slot) {
+ char stateFile[20];
+ engine_makeGameStateName(e, slot, stateFile, 20);
+ file_remove(stateFile, e->_saveDir);
+}
+
+const char* engine_getDataDir(struct Engine* e)
+{
+ return e->_dataDir;
+}