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/xworld/engine.c | 397 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 397 insertions(+) create mode 100644 apps/plugins/xworld/engine.c (limited to 'apps/plugins/xworld/engine.c') 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; +} -- cgit v1.2.3